From 241070003907177171d01690f68e9327ccc9507c Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Tue, 11 Nov 2025 16:01:12 +0100 Subject: [PATCH 01/35] Testing | Test for collapsing points with inner dented contour --- test/core/SmootherToolsTest.cpp | 74 ++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/test/core/SmootherToolsTest.cpp b/test/core/SmootherToolsTest.cpp index 78e2670..0fb1b63 100644 --- a/test/core/SmootherToolsTest.cpp +++ b/test/core/SmootherToolsTest.cpp @@ -1,4 +1,4 @@ -#include "gtest/gtest.h" +#include "gtest/gtest.h" #include "Smoother.h" #include "utils/Tools.h" @@ -299,6 +299,43 @@ class SmootherToolsTest : public ::testing::Test { return res; } + static Mesh buildCollapseEdgeMeshWithInnerDent() + { + // 7------------=6 + // | _-‾⟋⎸ + // | _-‾⟋ ⎸ + // | _-‾ ⟋ ⎸ + // |_-‾ ⟋ ⎸ + // 4---3---------5 + // | -‾‾- + // 2‾--__‾-_ + // 0------==1 + Mesh res; + res.grid = utils::GridTools::buildCartesianGrid(0.0, 1.0, 2); + res.coordinates = { + Coordinate({ 0.00, 0.00, 1.00 }), // 0 + Coordinate({ 0.60, 0.00, 1.00 }), // 1 + Coordinate({ 0.00, 0.10, 1.00 }), // 2 + Coordinate({ 0.20, 0.40, 1.00 }), // 3 + Coordinate({ 0.00, 0.40, 1.00 }), // 4 + Coordinate({ 1.00, 0.40, 1.00 }), // 5 + Coordinate({ 1.00, 1.00, 1.00 }), // 6 + Coordinate({ 0.00, 1.00, 1.00 }), // 7 + }; + + res.groups.push_back(Group()); + res.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 1, 3, 2 }, Element::Type::Surface), // 1 + Element({ 2, 3, 4 }, Element::Type::Surface), // 2 + Element({ 3, 5, 6 }, Element::Type::Surface), // 3 + Element({ 3, 6, 4 }, Element::Type::Surface), // 4 + Element({ 4, 6, 7 }, Element::Type::Surface), // 4 + }; + + return res; + } + static Mesh buildElementsToRemesh() { // 4 ---- 5 @@ -625,6 +662,41 @@ TEST_F(SmootherToolsTest, collapse_two_points_in_contour) EXPECT_EQ(collapsed[6], collapsed[7]); } +/// 7------------=6 +/// | _-‾⟋⎸ +/// | _-‾⟋ ⎸ +/// | _-‾ ⟋ ⎸ +/// |_-‾ ⟋ ⎸ +/// 4---3---------5 +/// | -‾‾- +/// 2‾--__‾-_ +/// 0------==1 +/// Expected result: +/// - Coordinate Ids 2 and 4 are removed from the list of coordinates. +/// - Coordinate Id 2 collapses to CoordinateId 0. +/// - Coordinate Id 4 collapses to CoordinateId 7. + +TEST_F(SmootherToolsTest, collapsePointsInContourWithInnerDent) +{ + Mesh mesh = buildCollapseEdgeMeshWithInnerDent(); + const Elements& elements = mesh.groups[0].elements; + + SmootherTools sT(mesh.grid); + Coordinates collapsed = + sT.collapsePointsOnContour(elements, mesh.coordinates, alignmentAngle); + + EXPECT_EQ(8, countDifferentCoordinates(mesh.coordinates)); + ASSERT_EQ(6, countDifferentCoordinates(collapsed)); + + + EXPECT_NE(collapsed[2], mesh.coordinates[2]); + EXPECT_NE(collapsed[4], mesh.coordinates[4]); + + EXPECT_NE(collapsed[2], collapsed[4]); + EXPECT_EQ(collapsed[2], collapsed[0]); + EXPECT_EQ(collapsed[4], collapsed[7]); +} + /// Build the list of singular Ids corresponding to a corner structure with the following ids /// \verbatim /// 2 ----- 6 From 28659e0d38188b92d0ec3d9a67d9ea7489660a5e Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Wed, 12 Nov 2025 17:53:05 +0100 Subject: [PATCH 02/35] Tessellator | Implement Splicer --- src/core/CMakeLists.txt | 1 + src/core/Splicer.cpp | 141 +++++++ src/core/Splicer.h | 23 ++ test/CMakeLists.txt | 1 + test/core/SplicerTest.cpp | 808 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 974 insertions(+) create mode 100644 src/core/Splicer.cpp create mode 100644 src/core/Splicer.h create mode 100644 test/core/SplicerTest.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d481f43..343d9bc 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(tessellator-core "Smoother.cpp" "SmootherTools.cpp" "Staircaser.cpp" + "Splicer.cpp" ) target_link_libraries(tessellator-core tessellator-utils) \ No newline at end of file diff --git a/src/core/Splicer.cpp b/src/core/Splicer.cpp new file mode 100644 index 0000000..5f4bb55 --- /dev/null +++ b/src/core/Splicer.cpp @@ -0,0 +1,141 @@ +#include "Splicer.h" +#include "utils/Geometry.h" + +namespace meshlib { +namespace core { +// using namespace utils; + +Splicer::Splicer(const Mesh& inputMesh) : GridTools(inputMesh.grid) { + this->mesh_.coordinates = inputMesh.coordinates; + + this->mesh_.groups.reserve(inputMesh.groups.size()); + for (auto& group : inputMesh.groups) { + Group newGroup; + newGroup.elements.reserve(group.elements.size()); + spliceTrianglesInGroup(newGroup, group); + + this->mesh_.groups.push_back(newGroup); + } + + + +} + +void Splicer::spliceTrianglesInGroup(Group & elementGroup, const Group& inputGroup) { + for (const auto& element : inputGroup.elements) { + if (!element.isTriangle()) { + continue; + } + + auto innerPointIds = getInnerPointIds(element); + + Elements firstPointTriangles = spliceTriangleIntoList(element, 0, innerPointIds[0]); + Elements secondPointTriangles = spliceTriangleIntoList(firstPointTriangles[firstPointTriangles.size() - 1], 1, innerPointIds[1]); + + Elements thirdPointTriangles; + + if (firstPointTriangles.size() != 1 || secondPointTriangles.size() == 1) { + thirdPointTriangles = spliceTriangleIntoList(firstPointTriangles[0], 2, innerPointIds[2]); + } + else { + thirdPointTriangles = spliceTriangleIntoList(secondPointTriangles[secondPointTriangles.size() - 1], 1, innerPointIds[2]); + } + + auto firstPointStart = firstPointTriangles.begin(); + if (thirdPointTriangles.size() != 1) { + ++firstPointStart; + } + + if (firstPointTriangles.size() != 1) { + elementGroup.elements.insert(elementGroup.elements.end(), firstPointStart, firstPointTriangles.end() - 1); + } + + auto secondPointEnd = secondPointTriangles.end(); + if (firstPointTriangles.size() == 1 && thirdPointTriangles.size() != 1) { + --secondPointEnd; + } + + elementGroup.elements.insert(elementGroup.elements.end(), secondPointTriangles.begin(), secondPointEnd); + + if (thirdPointTriangles.size() != 1) { + elementGroup.elements.insert(elementGroup.elements.end(), thirdPointTriangles.begin(), thirdPointTriangles.end()); + } + } +} + +std::vector> Splicer::getInnerPointIds(const Element& triangle) { + std::vector> innerPointIds(3); + + auto triV = utils::Geometry::asTriV(triangle, this->mesh_.coordinates); + for (CoordinateId c = 0; c < this->mesh_.coordinates.size(); ++c) { + if (std::find(triangle.vertices.begin(), triangle.vertices.end(), c) != triangle.vertices.end()) { + continue; + } + + auto& coordinate = this->mesh_.coordinates[c]; + + for (std::size_t vertexPosition = 0; vertexPosition < 3; ++vertexPosition) { + if (isBetween(triV[(vertexPosition + 1) % 3], triV[(vertexPosition + 2) % 3], coordinate)) { + double distance = (coordinate - triV[(vertexPosition + 1) % 3]).norm(); + innerPointIds[vertexPosition][distance] = c; + break; + } + } + } + + return innerPointIds; +} + + +bool Splicer::isBetween(const Coordinate& leftExtreme, const Coordinate& rightExtreme, const Coordinate& betweenPoint) { + auto extremesDistance = rightExtreme - leftExtreme; + auto betweenDistance = betweenPoint - leftExtreme; + auto crossProduct = betweenDistance ^ extremesDistance; + + if (!approxDir(crossProduct.norm(), 0.0)) { + return false; + } + + auto dotProduct = betweenDistance * extremesDistance; + + if (dotProduct < 0.0) { + return false; + } + + double extremeNorm = extremesDistance.norm(); + + if (dotProduct > (extremeNorm * extremeNorm)) { + return false; + } + + return true; +} + +Elements Splicer::spliceTriangleIntoList(const Element& triangle, std::size_t vertexPosition, const std::map& innerPoints) { + Elements newTriangles; + newTriangles.reserve(innerPoints.size() + 1); + + if (innerPoints.size() == 0) { + newTriangles.push_back(triangle); + return newTriangles; + } + + CoordinateId commonId = triangle.vertices[vertexPosition]; + CoordinateId leftExtremeId = triangle.vertices[(vertexPosition + 1) % 3]; + CoordinateId rightExtremeId = triangle.vertices[(vertexPosition + 2) % 3]; + + newTriangles.push_back(Element({ commonId, leftExtremeId, innerPoints.begin()->second }, Element::Type::Surface)); + auto secondPointIt = innerPoints.begin(); + auto thirdPointIt = secondPointIt; + ++thirdPointIt; + + for (; thirdPointIt != innerPoints.end(); ++secondPointIt, ++thirdPointIt) { + newTriangles.push_back(Element({ commonId, secondPointIt->second, thirdPointIt->second }, Element::Type::Surface)); + } + newTriangles.push_back(Element({ commonId, innerPoints.rbegin()->second, rightExtremeId }, Element::Type::Surface)); + + return newTriangles; +} + +} +} \ No newline at end of file diff --git a/src/core/Splicer.h b/src/core/Splicer.h new file mode 100644 index 0000000..556cc66 --- /dev/null +++ b/src/core/Splicer.h @@ -0,0 +1,23 @@ +#include "utils/GridTools.h" + + +namespace meshlib { +namespace core { + +class Splicer : public utils::GridTools{ +public: + Splicer(const Mesh&); + + Mesh getMesh() const { return this->mesh_; }; +private: + void spliceTrianglesInGroup(Group& elementGroup, const Group& inputGroup); + std::vector> getInnerPointIds(const Element&); + bool isBetween(const Coordinate& leftExtreme, const Coordinate& rightExtreme, const Coordinate& betweenPoint); + Elements spliceTriangleIntoList(const Element& triangle, std::size_t vertexPosition, const std::map& innerPoints); + + Mesh mesh_; +}; + +} +} + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cea5caa..82b55b1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable(tessellator_tests "core/SnapperTest.cpp" "core/SmootherTest.cpp" "core/SmootherToolsTest.cpp" + "core/SplicerTest.cpp" "core/StaircaserTest.cpp" "types/MeshTest.cpp" "utils/ConvexHullTest.cpp" diff --git a/test/core/SplicerTest.cpp b/test/core/SplicerTest.cpp new file mode 100644 index 0000000..4ae3fa9 --- /dev/null +++ b/test/core/SplicerTest.cpp @@ -0,0 +1,808 @@ +#include "gtest/gtest.h" +#include "utils/Geometry.h" +#include "utils/GridTools.h" +#include "core/Splicer.h" + +namespace meshlib::core { + +using namespace utils; + +class SplicerTest : public ::testing::Test { +public: +protected: + static void assertCoordinatesListEquals(const Coordinates& expectedCoordinates, const Coordinates& resultCoordinates) { + ASSERT_EQ(expectedCoordinates.size(), resultCoordinates.size()); + + for (CoordinateId c = 0; c < expectedCoordinates.size(); ++c) { + auto& expectedCoordinate = expectedCoordinates[c]; + auto& resultCoordinate = resultCoordinates[c]; + + for (Axis axis = X; axis <= Z; ++axis) { + EXPECT_EQ(expectedCoordinate[axis], resultCoordinate[axis]) + << "Current coordinate: #" << c << std::endl + << "Current Axis: #" << axis << std::endl; + } + } + } + + static void assertElementsListEquals(const Elements& expectedElements, const Elements& resultElements) { + ASSERT_EQ(expectedElements.size(), resultElements.size()); + + for (ElementId e = 0; e < expectedElements.size(); ++e) { + const Element& expectedElement = expectedElements[e]; + const Element& resultElement = resultElements[e]; + + EXPECT_EQ(resultElement.vertices.size(), expectedElement.vertices.size()) + << "Current Element: #" << e << std::endl; + EXPECT_EQ(resultElement.type, expectedElement.type) + << "Current Element: #" << e << std::endl; + + for (std::size_t v = 0; v < resultElement.vertices.size(); ++v) { + EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]) + << "Current Element: #" << e << std::endl + << "Current Vertex: #" << v << std::endl; + } + } + } +}; + +TEST_F(SplicerTest, spliceTriangleWithPointsInFirstSide) +{ + // + // . 0. + // /0\ /|\ + // / \ /⎹ ⎸\ + // / \ / | | \ + // / \ / ⎸ ⎹ \ + // / \ / ⎹ ⎸ \ + // / \ --> / | | \ + // / \ / ⎸ ⎹ \ + // / \ / ⎹ ⎸ \ + // /_________________\ /_____|_____|_____\ + // 1\ 3/\ 4/\ /2 1\ 3/\ 4/\ /2 + // \ / \ / \ / \ / \ / \ / + // \/____\ /____\/ \/____\ /____\/ + // 5 6 7 5 6 7 + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 4.00, 4.00, 0.00 }), // 3 + Coordinate({ 6.00, 4.00, 0.00 }), // 4 + Coordinate({ 3.00, 3.00, 0.00 }), // 5 + Coordinate({ 5.00, 3.00, 0.00 }), // 6 + Coordinate({ 7.00, 3.00, 0.00 }), // 7 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 1, 5, 3 }, Element::Type::Surface), // 1 + Element({ 1, 5, 3 }, Element::Type::Surface), // 2 + Element({ 3, 6, 4 }, Element::Type::Surface), // 3 + Element({ 2, 4, 7 }, Element::Type::Surface), // 4 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 0, 1, 3 }, Element::Type::Surface), // 0 + Element({ 0, 3, 4 }, Element::Type::Surface), // 1 + Element({ 0, 4, 2 }, Element::Type::Surface), // 2 + + Element({ 1, 5, 3 }, Element::Type::Surface), // 3 + Element({ 1, 5, 3 }, Element::Type::Surface), // 4 + Element({ 3, 6, 4 }, Element::Type::Surface), // 5 + Element({ 2, 4, 7 }, Element::Type::Surface), // 6 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, spliceTriangleWithPointsInSecondSide) +{ + // + // ._____7 ._____7 + // /0\ /\ /0\ /\ + // / \ / \ / \ / \ + // / \/____\6 / 4\/____\6 + // / 4\ /\ / ⎽⎼⎻⎺\ /\ + // / \ / \ / ⎽⎼⎻⎺ \ / \ + // / \/____\ --> / ⎽⎼⎻⎺ 3\/____\ + // / 3\ /5 / ⎽⎼⎻⎺ __-‾‾\ /5 + // / \ / / ⎽⎼⎻⎺___-‾‾ \ / + // /_________________\/ /__-______________\/ + // 1 2 1 2 + // + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 7.00, 5.00, 0.00 }), // 3 + Coordinate({ 6.00, 6.00, 0.00 }), // 4 + Coordinate({ 9.00, 5.00, 0.00 }), // 5 + Coordinate({ 8.00, 6.00, 0.00 }), // 6 + Coordinate({ 7.00, 7.00, 0.00 }), // 7 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 2, 5, 3 }, Element::Type::Surface), // 1 + Element({ 3, 6, 4 }, Element::Type::Surface), // 2 + Element({ 0, 4, 7 }, Element::Type::Surface), // 3 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 1, 2, 3 }, Element::Type::Surface), // 0 + Element({ 1, 3, 4 }, Element::Type::Surface), // 1 + Element({ 1, 4, 0 }, Element::Type::Surface), // 2 + + Element({ 2, 5, 3 }, Element::Type::Surface), // 3 + Element({ 3, 6, 4 }, Element::Type::Surface), // 4 + Element({ 0, 4, 7 }, Element::Type::Surface), // 5 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, spliceTriangleWithPointsInThirdSide) +{ + // + // 5______. 5______. + // /\ /0\ /\ /0\ + // / \ / \ / \ / \ + // 6/____\/ \ 6/____\/3 \ + // /\ /3 \ /\ / ⎺⎻⎼⎽ \ + // / \ / \ / \ / ⎺⎻⎼⎽ \ + // 7/____\/ \ -> 7/____\/4 ⎺⎻⎼⎽ \ + // \ /4 \ \ /‾‾--__ ⎺⎻⎼⎽ \ + // \ / \ \ / ‾‾-___⎺⎻⎼⎽\ + // \/_________________\ \/______________-__\ + // 1 2 1 2 + // + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 4.00, 6.00, 0.00 }), // 3 + Coordinate({ 3.00, 5.00, 0.00 }), // 4 + Coordinate({ 3.00, 7.00, 0.00 }), // 5 + Coordinate({ 2.00, 6.00, 0.00 }), // 6 + Coordinate({ 1.00, 5.00, 0.00 }), // 7 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 0, 5, 3 }, Element::Type::Surface), // 1 + Element({ 3, 6, 4 }, Element::Type::Surface), // 2 + Element({ 1, 4, 7 }, Element::Type::Surface), // 3 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 2, 0, 3 }, Element::Type::Surface), // 0 + Element({ 2, 3, 4 }, Element::Type::Surface), // 1 + Element({ 2, 4, 1 }, Element::Type::Surface), // 2 + + Element({ 0, 5, 3 }, Element::Type::Surface), // 3 + Element({ 3, 6, 4 }, Element::Type::Surface), // 4 + Element({ 1, 4, 7 }, Element::Type::Surface), // 5 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, spliceTriangleWithPointsInFirstAndSecondSides) +{ + // + // ._____12 0.______12 + // /0\ /\ /|\ /\ + // / \ / \ /⎹ ⎸\ / \ + // / \/____\11 / | |9\/____\11 + // / 9\ /\ / ⎸ ⎹ |\ /\ + // / \ / \ / ⎹ ⎸| \ / \ + // / \/____\ --> / | || \/____\ + // / 8\ /10 / ⎸ ⎹| 8/\ /10 + // / \ / / ⎹ ⎸ / \ / + // /_________________\/ /_____|_____|/____\/ + // 1\ 3/\ 4/\ /2 1\ 3/\ 4/\ /2 + // \ / \ / \ / \ / \ / \ / + // \/____\ /____\/ \/____\ /____\/ + // 5 6 7 5 6 7 + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 4.00, 4.00, 0.00 }), // 3 + Coordinate({ 6.00, 4.00, 0.00 }), // 4 + Coordinate({ 3.00, 3.00, 0.00 }), // 5 + Coordinate({ 5.00, 3.00, 0.00 }), // 6 + Coordinate({ 7.00, 3.00, 0.00 }), // 7 + Coordinate({ 7.00, 5.00, 0.00 }), // 8 + Coordinate({ 6.00, 6.00, 0.00 }), // 9 + Coordinate({ 9.00, 5.00, 0.00 }), // 10 + Coordinate({ 8.00, 6.00, 0.00 }), // 11 + Coordinate({ 7.00, 7.00, 0.00 }), // 12 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 1, 5, 3 }, Element::Type::Surface), // 1 + Element({ 1, 5, 3 }, Element::Type::Surface), // 2 + Element({ 3, 6, 4 }, Element::Type::Surface), // 3 + Element({ 2, 4, 7 }, Element::Type::Surface), // 4 + Element({ 2, 10, 8 }, Element::Type::Surface), // 5 + Element({ 8, 11, 9 }, Element::Type::Surface), // 6 + Element({ 0, 9, 12 }, Element::Type::Surface), // 7 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 0, 1, 3 }, Element::Type::Surface), // 0 + Element({ 0, 3, 4 }, Element::Type::Surface), // 1 + + Element({ 4, 2, 8 }, Element::Type::Surface), // 2 + Element({ 4, 8, 9 }, Element::Type::Surface), // 3 + Element({ 4, 9, 0 }, Element::Type::Surface), // 4 + + + Element({ 1, 5, 3 }, Element::Type::Surface), // 5 + Element({ 1, 5, 3 }, Element::Type::Surface), // 6 + Element({ 3, 6, 4 }, Element::Type::Surface), // 7 + Element({ 2, 4, 7 }, Element::Type::Surface), // 8 + Element({ 2, 10, 8 }, Element::Type::Surface), // 9 + Element({ 8, 11, 9 }, Element::Type::Surface), // 10 + Element({ 0, 9, 12 }, Element::Type::Surface), // 11 + }; +} + +TEST_F(SplicerTest, spliceTriangleWithPointsInSecondAndThirdSides) +{ + // + // 10______._____7 10______.______7 + // /\ /0\ /\ /\ /0\ /\ + // / \ / \ / \ / \ / \ / \ + // 11/____\/ \/____\6 11/____\/_____\/____\6 + // /\ /8 4\ /\ /\ /8 _-⎽⎼⎻⎺\4 /\ + // / \ / \ / \ / \ / _-⎽⎼⎻⎺ \ / \ + // 12/____\/ \/____\ --> 12/____\/_-‾⎽⎼⎻⎺ 3\/____\ + // \ /9 3\ /5 \ /9 ⎽⎼⎻⎺ __-‾‾\ /5 + // \ / \ / \ / ⎽⎼⎻⎺___-‾‾ \ / + // \/_________________\/ \/__-______________\/ + // 1 2 1 2 + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 7.00, 5.00, 0.00 }), // 3 + Coordinate({ 6.00, 6.00, 0.00 }), // 4 + Coordinate({ 9.00, 5.00, 0.00 }), // 5 + Coordinate({ 8.00, 6.00, 0.00 }), // 6 + Coordinate({ 7.00, 7.00, 0.00 }), // 7 + Coordinate({ 4.00, 6.00, 0.00 }), // 8 + Coordinate({ 3.00, 5.00, 0.00 }), // 9 + Coordinate({ 3.00, 7.00, 0.00 }), // 10 + Coordinate({ 2.00, 6.00, 0.00 }), // 11 + Coordinate({ 1.00, 5.00, 0.00 }), // 12 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 2, 5, 3 }, Element::Type::Surface), // 1 + Element({ 3, 6, 4 }, Element::Type::Surface), // 2 + Element({ 0, 4, 7 }, Element::Type::Surface), // 3 + Element({ 0, 10, 8 }, Element::Type::Surface), // 4 + Element({ 8, 11, 9 }, Element::Type::Surface), // 5 + Element({ 1, 9, 12 }, Element::Type::Surface), // 6 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 1, 2, 3 }, Element::Type::Surface), // 0 + Element({ 1, 3, 4 }, Element::Type::Surface), // 1 + + Element({ 4, 0, 8 }, Element::Type::Surface), // 2 + Element({ 4, 8, 9 }, Element::Type::Surface), // 3 + Element({ 4, 9, 1 }, Element::Type::Surface), // 4 + + + Element({ 2, 5, 3 }, Element::Type::Surface), // 5 + Element({ 3, 6, 4 }, Element::Type::Surface), // 6 + Element({ 0, 4, 7 }, Element::Type::Surface), // 7 + Element({ 0, 10, 8 }, Element::Type::Surface), // 8 + Element({ 8, 11, 9 }, Element::Type::Surface), // 9 + Element({ 1, 9, 12 }, Element::Type::Surface), // 10 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, spliceTriangleWithPointsInFirstAndThirdSides) +{ + // + // 10______. 10______.0 + // /\ /0\ /\ /|\ + // / \ / \ / \ /⎹ ⎸\ + // 11/____\/ \ 11/____\/ | | \ + // /\ /8 \ /\ 8/| ⎸ ⎹ \ + // / \ / \ / \ / |⎹ ⎸ \ + // 12/____\/ \ --> 12/____\/ || | \ + // \ /9 \ \ 9/\ |⎸ ⎹ \ + // \ / \ \ / \ ⎹ ⎸ \ + // \/_________________\ \/____\|_____|_____\ + // 1\ 3/\ 4/\ /2 1\ 3/\ 4/\ /2 + // \ / \ / \ / \ / \ / \ / + // \/____\ /____\/ \/____\ /____\/ + // 5 6 7 5 6 7 + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 4.00, 4.00, 0.00 }), // 3 + Coordinate({ 6.00, 4.00, 0.00 }), // 4 + Coordinate({ 3.00, 3.00, 0.00 }), // 5 + Coordinate({ 5.00, 3.00, 0.00 }), // 6 + Coordinate({ 7.00, 3.00, 0.00 }), // 7 + Coordinate({ 4.00, 6.00, 0.00 }), // 8 + Coordinate({ 3.00, 5.00, 0.00 }), // 9 + Coordinate({ 3.00, 7.00, 0.00 }), // 10 + Coordinate({ 2.00, 6.00, 0.00 }), // 11 + Coordinate({ 1.00, 5.00, 0.00 }), // 12 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 1, 5, 3 }, Element::Type::Surface), // 1 + Element({ 1, 5, 3 }, Element::Type::Surface), // 2 + Element({ 3, 6, 4 }, Element::Type::Surface), // 3 + Element({ 2, 4, 7 }, Element::Type::Surface), // 4 + Element({ 0, 10, 8 }, Element::Type::Surface), // 5 + Element({ 8, 11, 9 }, Element::Type::Surface), // 6 + Element({ 1, 9, 12 }, Element::Type::Surface), // 7 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 0, 3, 4 }, Element::Type::Surface), // 0 + Element({ 0, 4, 2 }, Element::Type::Surface), // 1 + + Element({ 3, 0, 8 }, Element::Type::Surface), // 2 + Element({ 3, 8, 9 }, Element::Type::Surface), // 3 + Element({ 3, 9, 1 }, Element::Type::Surface), // 4 + + + Element({ 1, 5, 3 }, Element::Type::Surface), // 5 + Element({ 1, 5, 3 }, Element::Type::Surface), // 6 + Element({ 3, 6, 4 }, Element::Type::Surface), // 7 + Element({ 2, 4, 7 }, Element::Type::Surface), // 8 + Element({ 0, 10, 8 }, Element::Type::Surface), // 9 + Element({ 8, 11, 9 }, Element::Type::Surface), // 10 + Element({ 1, 9, 12 }, Element::Type::Surface), // 11 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, spliceTriangleWithPointsInAllSides) +{ + // + // 15______._____12 ______.______ + // /\ /0\ /\ /\ /|\ /\ + // / \ / \ / \ / \ /⎹ ⎸\ / \ + // 16/____\/ \/____\11 /____\/ | | \/____\ + // /\ /13 9\ /\ /\ /| ⎸ ⎹ |\ /\ + // / \ / \ / \ / \ / |⎹ ⎸| \ / \ + // 17/____\/ \/____\ --> /____\/ || || \/____\ + // \ /14 8\ /10 \ /\ |⎸ ⎹| /\ / + // \ / \ / \ / \ ⎹ ⎸ / \ / + // \/_________________\/ \/____\|_____|/____\/ + // 1\ 3/\ 4/\ /2 \ /\ /\ / + // \ / \ / \ / \ / \ / \ / + // \/____\ /____\/ \/____\ /____\/ + // 5 6 7 + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 4.00, 4.00, 0.00 }), // 3 + Coordinate({ 6.00, 4.00, 0.00 }), // 4 + Coordinate({ 3.00, 3.00, 0.00 }), // 5 + Coordinate({ 5.00, 3.00, 0.00 }), // 6 + Coordinate({ 7.00, 3.00, 0.00 }), // 7 + Coordinate({ 7.00, 5.00, 0.00 }), // 8 + Coordinate({ 6.00, 6.00, 0.00 }), // 9 + Coordinate({ 9.00, 5.00, 0.00 }), // 10 + Coordinate({ 8.00, 6.00, 0.00 }), // 11 + Coordinate({ 7.00, 7.00, 0.00 }), // 12 + Coordinate({ 4.00, 6.00, 0.00 }), // 13 + Coordinate({ 3.00, 5.00, 0.00 }), // 14 + Coordinate({ 3.00, 7.00, 0.00 }), // 15 + Coordinate({ 2.00, 6.00, 0.00 }), // 16 + Coordinate({ 1.00, 5.00, 0.00 }), // 17 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 1, 5, 3 }, Element::Type::Surface), // 1 + Element({ 1, 5, 3 }, Element::Type::Surface), // 2 + Element({ 3, 6, 4 }, Element::Type::Surface), // 3 + Element({ 2, 4, 7 }, Element::Type::Surface), // 4 + Element({ 2, 10, 8 }, Element::Type::Surface), // 5 + Element({ 8, 11, 9 }, Element::Type::Surface), // 6 + Element({ 0, 9, 12 }, Element::Type::Surface), // 7 + Element({ 0, 15, 13 }, Element::Type::Surface), // 8 + Element({ 13, 16, 14 }, Element::Type::Surface), // 9 + Element({ 1, 14, 17 }, Element::Type::Surface), // 10 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 0, 3, 4 }, Element::Type::Surface), // 0 + + Element({ 4, 2, 8 }, Element::Type::Surface), // 1 + Element({ 4, 8, 9 }, Element::Type::Surface), // 2 + Element({ 4, 9, 0 }, Element::Type::Surface), // 3 + + + Element({ 3, 0, 13 }, Element::Type::Surface), // 4 + Element({ 3, 13, 14 }, Element::Type::Surface), // 5 + Element({ 3, 14, 1 }, Element::Type::Surface), // 6 + + + Element({ 1, 5, 3 }, Element::Type::Surface), // 6 + Element({ 1, 5, 3 }, Element::Type::Surface), // 7 + Element({ 3, 6, 4 }, Element::Type::Surface), // 8 + Element({ 2, 4, 7 }, Element::Type::Surface), // 9 + Element({ 2, 10, 8 }, Element::Type::Surface), // 10 + Element({ 8, 11, 9 }, Element::Type::Surface), // 11 + Element({ 0, 9, 12 }, Element::Type::Surface), // 12 + Element({ 0, 15, 13 }, Element::Type::Surface), // 13 + Element({ 13, 16, 14 }, Element::Type::Surface), // 14 + Element({ 1, 14, 17 }, Element::Type::Surface), // 15 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, spliceTriangleWithOnePointInFirstAndSecondSides) +{ + // + // ._____.8 0_____.8 + // /0\ ⎸ /|\ ⎸ + // / \ ⎸ / | \ ⎸ + // / \ ⎸ / | \ ⎸ + // / \ ⎸ / | \ ⎸ + // / 6\----.7 / | 6\----.7 + // / \ | --> / | / \ | + // / \ | / | / \ | + // / \ | / | / \ | + // /_________________\| /________|/_______\| + // 1 ⟍ 3/\ ⟋ 2 1 ⟍ 3/\ ⟋ 2 + // ⟍ / \ ⟋ ⟍ / \ ⟋ + // ⟍/____\⟋ ⟍/____\⟋ + // 4 5 4 5 + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 5.00, 4.00, 0.00 }), // 3 + Coordinate({ 4.00, 3.00, 0.00 }), // 4 + Coordinate({ 6.00, 3.00, 0.00 }), // 5 + Coordinate({ 6.50, 5.50, 0.00 }), // 6 + Coordinate({ 8.00, 5.50, 0.00 }), // 7 + Coordinate({ 6.50, 7.00, 0.00 }), // 8 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 3, 4, 5 }, Element::Type::Surface), // 1 + Element({ 2, 7, 6 }, Element::Type::Surface), // 2 + Element({ 0, 6, 8 }, Element::Type::Surface), // 3 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 0, 1, 3 }, Element::Type::Surface), // 0 + + Element({ 3, 2, 6 }, Element::Type::Surface), // 1 + Element({ 3, 6, 0 }, Element::Type::Surface), // 2 + + Element({ 3, 4, 5 }, Element::Type::Surface), // 3 + Element({ 2, 7, 6 }, Element::Type::Surface), // 4 + Element({ 0, 6, 8 }, Element::Type::Surface), // 5 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, spliceTriangleWithOnePointInSecondAndThirdSides) +{ + // + // 7._____._____.5 7._____0_____.5 + // ⎹ /0\ ⎸ ⎹ / \ ⎸ + // ⎹ / \ ⎸ ⎹ / \ ⎸ + // ⎹ / \ ⎸ ⎹ / \ ⎸ + // ⎹ / \ ⎸ ⎹6/ \3⎸ + // 8.----/6 3\----.4 8.----/---------\----.4 + // | / \ | --> | / _-‾ \ | + // | / \ | | / _-‾‾ \ | + // | / \ | | /__-‾‾ \ | + // |/_________________\| |/=________________\| + // 1 2 1 2 + + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 6.50, 5.50, 0.00 }), // 3 + Coordinate({ 8.00, 5.50, 0.00 }), // 4 + Coordinate({ 6.50, 7.00, 0.00 }), // 5 + Coordinate({ 3.50, 5.50, 0.00 }), // 6 + Coordinate({ 3.50, 7.00, 0.00 }), // 7 + Coordinate({ 2.00, 5.50, 0.00 }), // 8 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 2, 4, 3 }, Element::Type::Surface), // 1 + Element({ 0, 3, 5 }, Element::Type::Surface), // 2 + Element({ 0, 7, 6 }, Element::Type::Surface), // 3 + Element({ 1, 6, 8 }, Element::Type::Surface), // 4 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 1, 2, 3 }, Element::Type::Surface), // 0 + + Element({ 3, 0, 6 }, Element::Type::Surface), // 1 + Element({ 3, 6, 1 }, Element::Type::Surface), // 2 + + Element({ 2, 4, 3 }, Element::Type::Surface), // 3 + Element({ 0, 3, 5 }, Element::Type::Surface), // 4 + Element({ 0, 7, 6 }, Element::Type::Surface), // 5 + Element({ 1, 6, 8 }, Element::Type::Surface), // 6 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, spliceTriangleWithOnePointInFirstAndThirdSides) +{ + // + // 7._____. 7._____0 + // ⎹ /0\ ⎹ /|\ + // ⎹ / \ ⎹ / | \ + // ⎹ / \ ⎹ / | \ + // ⎹ / \ ⎹ / | \ + // 8 .----/6 \ 8 .----/6 | \ + // | / \ --> | / \ | \ + // | / \ | / \ | \ + // | / \ | / \ | \ + // |/_________________\ |/_______\|________\ + // 1 ⟍ 3/\ ⟋ 2 1 ⟍ 3/\ ⟋ 2 + // ⟍ / \ ⟋ ⟍ / \ ⟋ + // ⟍/____\⟋ ⟍/____\⟋ + // 4 5 4 5 + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 5.00, 4.00, 0.00 }), // 3 + Coordinate({ 4.00, 3.00, 0.00 }), // 4 + Coordinate({ 6.00, 3.00, 0.00 }), // 5 + Coordinate({ 3.50, 5.50, 0.00 }), // 6 + Coordinate({ 3.50, 7.00, 0.00 }), // 7 + Coordinate({ 2.00, 5.50, 0.00 }), // 8 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 3, 4, 5 }, Element::Type::Surface), // 1 + Element({ 0, 7, 6 }, Element::Type::Surface), // 2 + Element({ 1, 6, 8 }, Element::Type::Surface), // 3 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 0, 3, 2 }, Element::Type::Surface), // 0 + + Element({ 3, 0, 6 }, Element::Type::Surface), // 1 + Element({ 3, 6, 1 }, Element::Type::Surface), // 2 + + Element({ 3, 4, 5 }, Element::Type::Surface), // 3 + Element({ 0, 7, 6 }, Element::Type::Surface), // 4 + Element({ 1, 6, 8 }, Element::Type::Surface), // 5 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, spliceTriangleWithOnePointInAllSides) +{ + // + // 10._____._____.8 10._____0_____.8 + // ⎹ /0\ ⎸ ⎹ /|\ ⎸ + // ⎹ / \ ⎸ ⎹ / | \ ⎸ + // ⎹ / \ ⎸ ⎹ / | \ ⎸ + // ⎹ / \ ⎸ ⎹ / | \ ⎸ + // 11.----/9 6\----.7 11.----/9 | 6\----.7 + // | / \ | --> | / \ | / \ | + // | / \ | | / \ | / \ | + // | / \ | | / \ | / \ | + // |/_________________\| |/_______\|/_______\| + // 1 ⟍ 3/\ ⟋ 2 1 ⟍ 3/\ ⟋ 2 + // ⟍ / \ ⟋ ⟍ / \ ⟋ + // ⟍/____\⟋ ⟍/____\⟋ + // 4 5 4 5 + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + Coordinate({ 5.00, 4.00, 0.00 }), // 3 + Coordinate({ 4.00, 3.00, 0.00 }), // 4 + Coordinate({ 6.00, 3.00, 0.00 }), // 5 + Coordinate({ 6.50, 5.50, 0.00 }), // 6 + Coordinate({ 8.00, 5.50, 0.00 }), // 7 + Coordinate({ 6.50, 7.00, 0.00 }), // 8 + Coordinate({ 3.50, 5.50, 0.00 }), // 9 + Coordinate({ 3.50, 7.00, 0.00 }), // 10 + Coordinate({ 2.00, 5.50, 0.00 }), // 11 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 3, 4, 5 }, Element::Type::Surface), // 1 + Element({ 2, 7, 6 }, Element::Type::Surface), // 2 + Element({ 0, 6, 8 }, Element::Type::Surface), // 3 + Element({ 0, 10, 9 }, Element::Type::Surface), // 4 + Element({ 1, 9, 11 }, Element::Type::Surface), // 5 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 3, 2, 6 }, Element::Type::Surface), // 0 + Element({ 3, 6, 0 }, Element::Type::Surface), // 1 + + Element({ 3, 0, 9 }, Element::Type::Surface), // 2 + Element({ 3, 9, 1 }, Element::Type::Surface), // 3 + + Element({ 3, 4, 5 }, Element::Type::Surface), // 4 + Element({ 2, 7, 6 }, Element::Type::Surface), // 5 + Element({ 0, 6, 8 }, Element::Type::Surface), // 6 + Element({ 0, 10, 9 }, Element::Type::Surface), // 7 + Element({ 1, 9, 11 }, Element::Type::Surface), // 8 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + +TEST_F(SplicerTest, doNothingWithNormalTriangle) +{ + // 0 + // /\ + // / \ + // / \ + // / \ + // / \ + // / \ + // / \ + // / \ + // / \ + // /__________________\ + // 1 2 + + + Mesh inputMesh; + inputMesh.grid = GridTools::buildCartesianGrid(0.0, 10.0, 11); + inputMesh.coordinates = { + Coordinate({ 5.00, 7.00, 0.00 }), // 0 + Coordinate({ 2.00, 4.00, 0.00 }), // 1 + Coordinate({ 8.00, 4.00, 0.00 }), // 2 + }; + inputMesh.groups = { Group() }; + inputMesh.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + }; + + Coordinates expectedCoordinates(inputMesh.coordinates); + + Mesh outputMesh; + ASSERT_NO_THROW(outputMesh = Splicer{ inputMesh }.getMesh()); + + Elements expectedElements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + }; + + assertCoordinatesListEquals(expectedCoordinates, outputMesh.coordinates); + assertElementsListEquals(expectedElements, outputMesh.groups[0].elements); +} + + + +} \ No newline at end of file From 190e77793fdc6329b2ab6104f0fff61b4212b835 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Mon, 17 Nov 2025 17:23:13 +0100 Subject: [PATCH 03/35] Tessellator | Implement method to remove null area triangles --- src/utils/MeshTools.cpp | 118 +++++++++++++++++++ src/utils/MeshTools.h | 1 + test/utils/MeshToolsTest.cpp | 214 ++++++++++++++++++++++++++++++++++- 3 files changed, 331 insertions(+), 2 deletions(-) diff --git a/src/utils/MeshTools.cpp b/src/utils/MeshTools.cpp index b2a25f4..e946826 100644 --- a/src/utils/MeshTools.cpp +++ b/src/utils/MeshTools.cpp @@ -5,6 +5,7 @@ #include "GridTools.h" #include "ElemGraph.h" #include "CoordGraph.h" +#include #include @@ -418,4 +419,121 @@ bool isAClosedTopology(const Elements& es) return CoordGraph(es).getBoundaryGraph().getVertices().size() == 0; } +std::map> getAdjacentMap(const Elements& elements) { + std::multimap, std::pair> sidesToElems; + for (ElementId e = 0; e < elements.size(); ++e) { + const Element& element = elements[e]; + if (!element.isTriangle()) { + continue; + } + for (std::size_t v = 0; v < element.vertices.size(); v++) { + std::pair ids({ element.vertices[v], element.vertices[(v + 1) % 3] }); + if (ids.first > ids.second) { + std::swap(ids.first, ids.second); + } + sidesToElems.emplace(ids, std::pair(e, v)); + } + } + + std::map> adjacentMap; + for (auto it = sidesToElems.begin(); it != sidesToElems.end(); ++it) { + auto nIt = std::next(it); + if (nIt == sidesToElems.end() || it->first != nIt->first) { + continue; + } + ElementId eId1 = it->second.first; + ElementId eId2 = nIt->second.first; + if (!Geometry::areAdjacentWithSameTopologicalOrientation( + elements[eId1], elements[eId2])) { + continue; + } + + adjacentMap.try_emplace(eId1, std::vector{ eId1, eId1, eId1 }); + adjacentMap.try_emplace(eId2, std::vector{ eId2, eId2, eId2 }); + adjacentMap[eId1][it->second.second] = eId2; + adjacentMap[eId2][nIt->second.second] = eId1; + } + + return adjacentMap; +} + +Mesh removeNullAreasKeepingTopology(const Mesh & inputMesh, double tolerance) { + Mesh resultMesh(inputMesh); + + for (auto& group : resultMesh.groups) { + std::map> adjacentMap = getAdjacentMap(group.elements); + + std::set longestAdjacentTreated; + bool areaZeroFound = true; + std::size_t tries = 0; + for (; areaZeroFound && tries < 100000; ++tries) { + areaZeroFound = false; + for (auto it = adjacentMap.begin(); it != adjacentMap.end(); ++it) { + ElementId e = it->first; + + if (longestAdjacentTreated.find(e) != longestAdjacentTreated.end()) { + continue; + } + + Element& element = group.elements[e]; + + double area = Geometry::area(Geometry::asTriV(element, inputMesh.coordinates)); + if (std::abs(area) <= tolerance) { + areaZeroFound = true; + + std::size_t longestSideIndex = 0; + double longestSide = 0; + + for (std::size_t v = 0; v < 3; ++v) { + double side = (inputMesh.coordinates[element.vertices[(v + 1) % 3]] - inputMesh.coordinates[element.vertices[v]]).norm(); + if (longestSide < side) { + longestSide = side; + longestSideIndex = v; + } + } + ElementId longestAdjacent = it->second[longestSideIndex]; + longestAdjacentTreated.insert(e); + longestAdjacentTreated.insert(longestAdjacent); + + std::size_t longestAdjacentVertexStart = std::find(adjacentMap[longestAdjacent].begin(), adjacentMap[longestAdjacent].end(), e) - adjacentMap[longestAdjacent].begin(); + std::size_t longestAdjacentVertexEnd = (longestAdjacentVertexStart + 1) % 3; + + CoordinateId longestSideStart = element.vertices[longestSideIndex]; + CoordinateId longestSideEnd = element.vertices[(longestSideIndex + 1) % 3]; + CoordinateId middlePoint = element.vertices[(longestSideIndex + 2) % 3]; + CoordinateId adjacentOpposite = group.elements[longestAdjacent].vertices[(longestAdjacentVertexStart + 2) % 3]; + + + group.elements[longestAdjacent].vertices[longestAdjacentVertexEnd] = middlePoint; + element.vertices[(longestSideIndex + 1) % 3] = adjacentOpposite; + + ElementId auxAdjacent = it->second[(longestSideIndex + 1) % 3]; + + it->second[longestSideIndex] = adjacentMap[longestAdjacent][longestAdjacentVertexEnd]; + if (it->second[longestSideIndex] == longestAdjacent) { + it->second[longestSideIndex] = e; + } + else { + std::size_t newAdjacentStart = std::find(adjacentMap[it->second[longestSideIndex]].begin(), adjacentMap[it->second[longestSideIndex]].end(), longestAdjacent) - adjacentMap[it->second[longestSideIndex]].begin(); + adjacentMap[it->second[longestSideIndex]][newAdjacentStart] = e; + } + it->second[(longestSideIndex + 1) % 3] = longestAdjacent; + + if (auxAdjacent == e) { + auxAdjacent = longestAdjacent; + } + else { + std::size_t newAdjacentStart = std::find(adjacentMap[auxAdjacent].begin(), adjacentMap[auxAdjacent].end(), e) - adjacentMap[auxAdjacent].begin(); + adjacentMap[auxAdjacent][newAdjacentStart] = longestAdjacent; + } + adjacentMap[longestAdjacent][longestAdjacentVertexStart] = auxAdjacent; + adjacentMap[longestAdjacent][longestAdjacentVertexEnd] = e; + } + } + } + } + + return resultMesh; +} + } \ No newline at end of file diff --git a/src/utils/MeshTools.h b/src/utils/MeshTools.h index 688c0cb..e84e4c2 100644 --- a/src/utils/MeshTools.h +++ b/src/utils/MeshTools.h @@ -8,6 +8,7 @@ namespace meshlib::utils::meshTools { Mesh duplicateCoordinatesUsedByDifferentGroups(const Mesh& mesh); Mesh duplicateCoordinatesSharedBySingleTrianglesVertex(const Mesh& mesh); +Mesh removeNullAreasKeepingTopology(const Mesh& mesh, double tolerance = 0.001); static bool isNode(const Element& e) { return e.isNode(); } static bool isNotNode(const Element& e) { return !e.isNode(); } diff --git a/test/utils/MeshToolsTest.cpp b/test/utils/MeshToolsTest.cpp index b46c30a..9338dfa 100644 --- a/test/utils/MeshToolsTest.cpp +++ b/test/utils/MeshToolsTest.cpp @@ -1,4 +1,4 @@ - + #include "gtest/gtest.h" #include "core/Slicer.h" @@ -12,7 +12,40 @@ using namespace meshFixtures; class MeshToolsTest : public ::testing::Test { public: - + static void assertCoordinatesListEquals(const Coordinates& expectedCoordinates, const Coordinates& resultCoordinates) { + ASSERT_EQ(expectedCoordinates.size(), resultCoordinates.size()); + + for (CoordinateId c = 0; c < expectedCoordinates.size(); ++c) { + auto& expectedCoordinate = expectedCoordinates[c]; + auto& resultCoordinate = resultCoordinates[c]; + + for (Axis axis = X; axis <= Z; ++axis) { + EXPECT_EQ(expectedCoordinate[axis], resultCoordinate[axis]) + << "Current coordinate: #" << c << std::endl + << "Current Axis: #" << axis << std::endl; + } + } + } + + static void assertElementsListEquals(const Elements& expectedElements, const Elements& resultElements) { + ASSERT_EQ(expectedElements.size(), resultElements.size()); + + for (ElementId e = 0; e < expectedElements.size(); ++e) { + const Element& expectedElement = expectedElements[e]; + const Element& resultElement = resultElements[e]; + + EXPECT_EQ(resultElement.vertices.size(), expectedElement.vertices.size()) + << "Current Element: #" << e << std::endl; + EXPECT_EQ(resultElement.type, expectedElement.type) + << "Current Element: #" << e << std::endl; + + for (std::size_t v = 0; v < resultElement.vertices.size(); ++v) { + EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]) + << "Current Element: #" << e << std::endl + << "Current Vertex: #" << v << std::endl; + } + } + } }; @@ -720,4 +753,181 @@ TEST_F(MeshToolsTest, reduceGrid_epsilon_coord) } } +// +// 5---------ʌ---------6 5---------ʌ---------6 +// | /0\ | | /0\ | +// | e4 / \ | | e4 / | \ e5 | +// | / e0 \ e5 | | / ⎸ \ | +// | / \ | | / | \ | +// | / \ | | / ⎸ \ | +// | / \ | --> | / e0 | e3\ | +// | / \ | | / ⎸ \ | +// | / ve3 4 \ | | / |4 \ | +// |/_________________\| |/___________|_____\| +// 1 ‾==‾‾‾‾‾‾‾‾/‾‾‾‾=‾2 1 ‾-_ / -‾2 +// ‾-_ e1 /e2_-‾ ‾-_ e1 /e2_-‾ +// ‾-_/_-‾ ‾-_/_-‾ +// 3 3 + +TEST_F(MeshToolsTest, removeNullAreaTriangles) +{ + Mesh m; + + m.coordinates = { + Coordinate({1.5, 4.0, 0.0}), // 0 + Coordinate({0.0, 1.0, 0.0}), // 1 + Coordinate({3.0, 1.0, 0.0}), // 2 + Coordinate({3.0, 0.0, 0.0}), // 3 + Coordinate({2.0, 1.0, 0.0}), // 4 + Coordinate({0.0, 4.0, 0.0}), // 5 + Coordinate({3.0, 4.0, 0.0}), // 6 + }; + m.groups.resize(1); + m.groups[0].elements = { + Element({0, 1, 2}, Element::Type::Surface), // 0 + Element({1, 3, 4}, Element::Type::Surface), // 1 + Element({2, 4, 3}, Element::Type::Surface), // 2 + Element({1, 4, 2}, Element::Type::Surface), // 3 + Element({0, 5, 1}, Element::Type::Surface), // 4 + Element({0, 2, 6}, Element::Type::Surface), // 5 + }; + + auto expectedCoordinates = m.coordinates; + + Elements expectedElements = { + Element({0, 1, 4}, Element::Type::Surface), // 0 + Element({1, 3, 4}, Element::Type::Surface), // 1 + Element({2, 4, 3}, Element::Type::Surface), // 2 + Element({0, 4, 2}, Element::Type::Surface), // 3 + Element({0, 5, 1}, Element::Type::Surface), // 4 + Element({0, 2, 6}, Element::Type::Surface), // 5 + }; + + auto resultMesh = meshTools::removeNullAreasKeepingTopology(m); + + assertCoordinatesListEquals(expectedCoordinates, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); +} + +// 4 4 +// 3┌─────────╥─6 3┌─────────┬─6 +// │\ ⫽ ║/ │\ /│/ +// │ \ ⫽ ║\5 │ \ /╱│\5 +// │ \ ⫽ ║ \ │ \ /╱ │ \ +// 2│___\⫽ ║ \ -> 2│___\1/╱ │ \ +// │ 1⫽ ║ \ │ /|\ │ \ +// │ ⫽ ║ \ │ / | \ │ \ +// │ ⫽ ║ \ │ / | \ │ \ +// │⫽ 10 ║ \ │/ |10 \│ \ +// 0╘════ʌ════╝───────8 0└────ʌ────┴───────8 +// \ / \ /7 \ / \ /7 +// \/ \/ \/ \/ +// 9 11 9 11 +// + +TEST_F(MeshToolsTest, removeNullAreaTrianglesWithMultipleAdjacents) +{ + Mesh m; + + m.coordinates = { + Coordinate({ 0.0, 2.0, 0.0 }), // 0 + Coordinate({ 4.0, 6.0, 0.0 }), // 1 + Coordinate({ 0.0, 6.0, 0.0 }), // 2 + Coordinate({ 0.0, 12.0, 0.0 }), // 3 + Coordinate({ 10.0, 12.0, 0.0 }), // 4 + Coordinate({ 10.0, 9.0, 0.0 }), // 5 + Coordinate({ 12.0, 12.0, 0.0 }), // 6 + Coordinate({ 10.0, 2.0, 0.0 }), // 7 + Coordinate({ 19.0, 2.0, 0.0 }), // 8 + Coordinate({ 2.0, 0.0, 0.0 }), // 9 + Coordinate({ 4.0, 2.0, 0.0 }), // 10 + Coordinate({ 6.0, 0.0, 0.0 }), // 11 + }; + m.groups.resize(1); + m.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 2, 1, 3 }, Element::Type::Surface), // 1 + Element({ 3, 1, 4 }, Element::Type::Surface), // 2 + Element({ 0, 4, 1 }, Element::Type::Surface), // 3 + Element({ 4, 5, 6 }, Element::Type::Surface), // 4 + Element({ 5, 7, 8 }, Element::Type::Surface), // 5 + Element({ 0, 7, 4 }, Element::Type::Surface), // 6 + Element({ 4, 7, 5 }, Element::Type::Surface), // 7 + Element({ 0, 9, 10 }, Element::Type::Surface), // 8 + Element({ 10, 11, 7 }, Element::Type::Surface), // 9 + Element({ 0, 10, 7 }, Element::Type::Surface), // 10 + }; + + auto expectedCoordinates = m.coordinates; + + Elements expectedElements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 + Element({ 2, 1, 3 }, Element::Type::Surface), // 1 + Element({ 3, 1, 4 }, Element::Type::Surface), // 2 + Element({ 0, 10, 1 }, Element::Type::Surface), // 3 + Element({ 4, 5, 6 }, Element::Type::Surface), // 4 + Element({ 5, 7, 8 }, Element::Type::Surface), // 5 + Element({ 1, 7, 5 }, Element::Type::Surface), // 6 + Element({ 4, 1, 5 }, Element::Type::Surface), // 7 + Element({ 0, 9, 10 }, Element::Type::Surface), // 8 + Element({ 10, 11, 7 }, Element::Type::Surface), // 9 + Element({ 1, 10, 7 }, Element::Type::Surface), // 10 + }; + + auto resultMesh = meshTools::removeNullAreasKeepingTopology(m); + + assertCoordinatesListEquals(expectedCoordinates, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); +} + +// +// 4* ʌ *5 4* ʌ *5 +// | /0\ | /0\ +// | / \ | / | \ +// | / \ | / ⎸ \ +// | / \ | / | \ +// | / \ | / ⎸ \ +// | / \ --> | / | \ +// | / \ | / ⎸ \ +// | / \ | / | \ +// |/_________________\ |/___________|_____\ +// 1‾‾‾‾‾‾‾‾‾‾‾‾3‾‾‾‾‾‾2 1 3 2 + +TEST_F(MeshToolsTest, removeNullAreaTrianglesWithNodesAndLines) +{ + Mesh m; + + m.coordinates = { + Coordinate({1.5, 4.0, 0.0}), // 0 + Coordinate({0.0, 1.0, 0.0}), // 1 + Coordinate({3.0, 1.0, 0.0}), // 2 + Coordinate({2.0, 1.0, 0.0}), // 3 + Coordinate({0.0, 4.0, 0.0}), // 4 + Coordinate({3.0, 4.0, 0.0}), // 5 + }; + m.groups.resize(1); + m.groups[0].elements = { + Element({0, 1, 2}, Element::Type::Surface), // 0 + Element({1, 3, 2}, Element::Type::Surface), // 1 + Element({4, 1}, Element::Type::Line), // 2 + Element({1, 4}, Element::Type::Line), // 3 + Element({5}, Element::Type::Node), // 4 + }; + + auto expectedCoordinates = m.coordinates; + + Elements expectedElements = { + Element({0, 1, 3}, Element::Type::Surface), // 0 + Element({0, 3, 2}, Element::Type::Surface), // 1 + Element({4, 1}, Element::Type::Line), // 2 + Element({1, 4}, Element::Type::Line), // 3 + Element({5}, Element::Type::Node), // 4 + }; + + auto resultMesh = meshTools::removeNullAreasKeepingTopology(m); + + assertCoordinatesListEquals(expectedCoordinates, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); +} + } \ No newline at end of file From a22ecac5de14193203064215aac034087d5b1e11 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Tue, 18 Nov 2025 14:20:07 +0100 Subject: [PATCH 04/35] Tessellator | Transform RedundancyCleaner into a namespace --- src/cgal/Manifolder.cpp | 2 +- src/cgal/Repairer.cpp | 6 +- src/core/Collapser.cpp | 10 +-- src/core/Slicer.cpp | 6 +- src/core/Smoother.cpp | 10 +-- src/core/Staircaser.cpp | 58 +++++++------- src/meshers/ConformalMesher.cpp | 2 +- src/meshers/OffgridMesher.cpp | 2 +- src/meshers/StaircaseMesher.cpp | 2 +- src/utils/MeshTools.cpp | 4 +- src/utils/RedundancyCleaner.cpp | 26 +++--- src/utils/RedundancyCleaner.h | 35 ++++---- test/core/SlicerTest.cpp | 114 ++++++++++++++++++++++++++- test/meshers/StaircaseMesherTest.cpp | 8 +- test/utils/RedundancyCleanerTest.cpp | 18 ++--- 15 files changed, 205 insertions(+), 98 deletions(-) diff --git a/src/cgal/Manifolder.cpp b/src/cgal/Manifolder.cpp index e8b13aa..dbfaa6e 100644 --- a/src/cgal/Manifolder.cpp +++ b/src/cgal/Manifolder.cpp @@ -100,7 +100,7 @@ Mesh Manifolder::getSurfacesMesh() const { Mesh m = getClosedSurfacesMesh(); utils::meshTools::mergeMesh(m, getOpenSurfacesMesh()); - utils::RedundancyCleaner::cleanCoords(m); + utils::redundancyCleaner::cleanCoords(m); return m; } diff --git a/src/cgal/Repairer.cpp b/src/cgal/Repairer.cpp index 9b36973..773ea09 100644 --- a/src/cgal/Repairer.cpp +++ b/src/cgal/Repairer.cpp @@ -72,9 +72,9 @@ Mesh repair(const Mesh& m) repairGroup(r.coordinates, r.groups[gId], m.coordinates, g); } - RedundancyCleaner::fuseCoords(r); - RedundancyCleaner::removeDegenerateElements(r); - RedundancyCleaner::cleanCoords(r); + redundancyCleaner::fuseCoords(r); + redundancyCleaner::removeDegenerateElements(r); + redundancyCleaner::cleanCoords(r); return r; } diff --git a/src/core/Collapser.cpp b/src/core/Collapser.cpp index 6a2e8c4..398f0ff 100644 --- a/src/core/Collapser.cpp +++ b/src/core/Collapser.cpp @@ -26,11 +26,11 @@ Collapser::Collapser(const Mesh& in, int decimalPlaces, const std::vector& dimensionPol ); } - RedundancyCleaner::removeElementsWithCondition(mesh_, [](auto e) {return !(e.isTriangle() || e.isLine() || e.isNode()); }); - RedundancyCleaner::fuseCoords(mesh_); - RedundancyCleaner::removeDegenerateElements(mesh_); + redundancyCleaner::removeElementsWithCondition(mesh_, [](auto e) {return !(e.isTriangle() || e.isLine() || e.isNode()); }); + redundancyCleaner::fuseCoords(mesh_); + redundancyCleaner::removeDegenerateElements(mesh_); // Checks ensured post conditions. meshTools::checkNoCellsAreCrossed(mesh_); diff --git a/src/core/Smoother.cpp b/src/core/Smoother.cpp index 9a15452..49b2044 100644 --- a/src/core/Smoother.cpp +++ b/src/core/Smoother.cpp @@ -77,10 +77,10 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : } - RedundancyCleaner::fuseCoords(res); - RedundancyCleaner::removeDegenerateElements(res); + redundancyCleaner::fuseCoords(res); + redundancyCleaner::removeDegenerateElements(res); res = buildMeshFilteringElements(res, isTriangle); - RedundancyCleaner::cleanCoords(res); + redundancyCleaner::cleanCoords(res); mesh_ = res; @@ -88,8 +88,8 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : for (auto const& g : mesh_.groups) { cs = sT_.collapsePointsOnContour(g.elements, cs, opts_.contourAlignmentAngle); } - RedundancyCleaner::fuseCoords(mesh_); - RedundancyCleaner::removeDegenerateElements(mesh_); + redundancyCleaner::fuseCoords(mesh_); + redundancyCleaner::removeDegenerateElements(mesh_); meshTools::checkNoCellsAreCrossed(mesh_); } diff --git a/src/core/Staircaser.cpp b/src/core/Staircaser.cpp index 40c9d00..5b3bddf 100644 --- a/src/core/Staircaser.cpp +++ b/src/core/Staircaser.cpp @@ -41,9 +41,9 @@ Mesh Staircaser::getMesh() } } - RedundancyCleaner::fuseCoords(mesh_); - RedundancyCleaner::removeDegenerateElements(mesh_); - RedundancyCleaner::cleanCoords(mesh_); + redundancyCleaner::fuseCoords(mesh_); + redundancyCleaner::removeDegenerateElements(mesh_); + redundancyCleaner::cleanCoords(mesh_); return mesh_; } @@ -82,14 +82,14 @@ IdSet findCommonNeighborsVertices(const Mesh& mesh, const std::pairvertices; auto it = std::find(elementVertices.begin(), elementVertices.end(), vertex1); - + if (it != elementVertices.end()) { auto pos = std::distance(elementVertices.begin(), it); auto n = elementVertices.size(); - - neighborsOfVertex1.insert(elementVertices[(pos + n - 1) % n]); - neighborsOfVertex1.insert(elementVertices[(pos + 1) % n]); - } + + neighborsOfVertex1.insert(elementVertices[(pos + n - 1) % n]); + neighborsOfVertex1.insert(elementVertices[(pos + 1) % n]); + } } } } @@ -102,13 +102,13 @@ IdSet findCommonNeighborsVertices(const Mesh& mesh, const std::pairvertices; auto it = std::find(elementVertices.begin(), elementVertices.end(), vertex2); - + if (it != elementVertices.end()) { auto pos = std::distance(elementVertices.begin(), it); auto n = elementVertices.size(); - - neighborsOfVertex2.insert(elementVertices[(pos + n - 1) % n]); - neighborsOfVertex2.insert(elementVertices[(pos + 1) % n]); + + neighborsOfVertex2.insert(elementVertices[(pos + n - 1) % n]); + neighborsOfVertex2.insert(elementVertices[(pos + 1) % n]); } } } @@ -178,14 +178,14 @@ Mesh Staircaser::getSelectiveMesh(const std::set& cellsToStructure, GapsFi continue; } for (const auto e : elements) { - auto [newElement, boundaryCoordinates] = obtainNewIndexForElement(*e, cellsToStructure, coordinateMap); + auto [newElement, boundaryCoordinates] = obtainNewIndexForElement(*e, cellsToStructure, coordinateMap); auto allCoordsOnBoundary = isAllCoordinatesOnTheSameCellBoundary(newElement, cellsToStructure); if (!allCoordsOnBoundary) { meshGroup.elements.push_back(newElement); cellElemMap_withNewElements[cell].push_back(&meshGroup.elements.back()); - + for (size_t i = 0; i < boundaryCoordinates.size(); ++i) { for (size_t j = i + 1; j < boundaryCoordinates.size(); ++j) { boundaryCoordinatePairs.emplace(boundaryCoordinates[i], boundaryCoordinates[j]); @@ -199,15 +199,15 @@ Mesh Staircaser::getSelectiveMesh(const std::set& cellsToStructure, GapsFi auto elementsConvertedInLines = getElementsConvertedInLines(cell, cellElemMap_withNewElements); splitLinesWithNeighborTriangle(g, elementsConvertedInLines, meshGroup, cell, cellElemMap_withNewElements, toRemove); - + } } } - - RedundancyCleaner::fuseCoords(mesh_); - RedundancyCleaner::removeDegenerateElements(mesh_); - RedundancyCleaner::removeRepeatedElements(mesh_); - RedundancyCleaner::cleanCoords(mesh_); + + redundancyCleaner::fuseCoords(mesh_); + redundancyCleaner::removeDegenerateElements(mesh_); + redundancyCleaner::removeRepeatedElements(mesh_); + redundancyCleaner::cleanCoords(mesh_); for (auto it = boundaryCoordinatePairs.begin(); it != boundaryCoordinatePairs.end();) { const auto& [coord1, coord2] = *it; @@ -354,7 +354,7 @@ void Staircaser::fillGaps(const RelativePairSet boundaryCoordinatePairs) { } } - RedundancyCleaner::removeElements(mesh_, toRemove); + redundancyCleaner::removeElements(mesh_, toRemove); } } } else { @@ -362,7 +362,7 @@ void Staircaser::fillGaps(const RelativePairSet boundaryCoordinatePairs) { } } - RedundancyCleaner::removeDegenerateElements(mesh_); + redundancyCleaner::removeDegenerateElements(mesh_); } std::pair Staircaser::obtainNewIndexForElement(const Element& e, const std::set& cellsToStructure, CoordinateMap& coordinateMap) { @@ -517,7 +517,7 @@ void Staircaser::splitLinesWithNeighborTriangle(const size_t groupIndex, std::se bool shareExactlyTwo = (sharedCount == 2); if(shareExactlyTwo && otherElementsInCell->isTriangle()) { - + auto v1 = *sharedVertices.begin(); auto v2 = *std::next(sharedVertices.begin()); @@ -527,7 +527,7 @@ void Staircaser::splitLinesWithNeighborTriangle(const size_t groupIndex, std::se if(!correctOrientation) { std::swap(v1, v2); - } + } CoordinateId v4 = -1; for (const auto& v : e.vertices) { @@ -552,7 +552,7 @@ void Staircaser::splitLinesWithNeighborTriangle(const size_t groupIndex, std::se Element triangle2; triangle2.type = Element::Type::Surface; triangle2.vertices = { v4, v1, v3 }; - + auto itOtherElement = std::find(meshGroup.elements.begin(), meshGroup.elements.end(), *otherElementsInCell); if (itOtherElement != meshGroup.elements.end()) { ElementId otherElementId = std::distance(meshGroup.elements.begin(), itOtherElement); @@ -568,7 +568,7 @@ void Staircaser::splitLinesWithNeighborTriangle(const size_t groupIndex, std::se meshGroup.elements.push_back(triangle1); meshGroup.elements.push_back(triangle2); - RedundancyCleaner::removeElements(mesh_, toRemove); + redundancyCleaner::removeElements(mesh_, toRemove); toRemove[groupIndex].clear(); processed = true; @@ -605,9 +605,9 @@ void Staircaser::processTriangleAndAddToGroup(const Element& triangle, const Rel } } - RedundancyCleaner::fuseCoords(auxiliarMesh); - RedundancyCleaner::removeDegenerateElements(auxiliarMesh); - RedundancyCleaner::cleanCoords(auxiliarMesh); + redundancyCleaner::fuseCoords(auxiliarMesh); + redundancyCleaner::removeDegenerateElements(auxiliarMesh); + redundancyCleaner::cleanCoords(auxiliarMesh); std::map idSetByCellSurface; std::map relativeIdsByCellSurface; diff --git a/src/meshers/ConformalMesher.cpp b/src/meshers/ConformalMesher.cpp index dca455c..06490b8 100644 --- a/src/meshers/ConformalMesher.cpp +++ b/src/meshers/ConformalMesher.cpp @@ -197,7 +197,7 @@ Mesh ConformalMesher::mesh() const // Calls structurer to mesh only those cells. log("Structuring non-conformal cells.", 1); res = Staircaser{ res }.getSelectiveMesh(nonConformalCells,Staircaser::GapsFillingType::Insert); - RedundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(res); + redundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(res); logNumberOfTriangles(countMeshElementsIf(res, isTriangle)); // Find cells which break conformal FDTD rules after selective structuring. diff --git a/src/meshers/OffgridMesher.cpp b/src/meshers/OffgridMesher.cpp index e7c8914..a291869 100644 --- a/src/meshers/OffgridMesher.cpp +++ b/src/meshers/OffgridMesher.cpp @@ -70,7 +70,7 @@ Mesh OffgridMesher::mesh() const logNumberOfTriangles(countMeshElementsIf(res, isTriangle)); reduceGrid(res, originalGrid_); - RedundancyCleaner::cleanCoords(res); + redundancyCleaner::cleanCoords(res); log("Primal mesh built succesfully.", 1); return res; diff --git a/src/meshers/StaircaseMesher.cpp b/src/meshers/StaircaseMesher.cpp index 7ad6458..6ed4fec 100644 --- a/src/meshers/StaircaseMesher.cpp +++ b/src/meshers/StaircaseMesher.cpp @@ -67,7 +67,7 @@ void StaircaseMesher::process(Mesh& mesh) const logNumberOfLines(countMeshElementsIf(mesh, isLine)); log("Removing repeated and overlapping elements.", 1); - RedundancyCleaner::removeOverlappedElementsByDimension(mesh, dimensions); + redundancyCleaner::removeOverlappedElementsByDimension(mesh, dimensions); logNumberOfQuads(countMeshElementsIf(mesh, isQuad)); logNumberOfLines(countMeshElementsIf(mesh, isLine)); diff --git a/src/utils/MeshTools.cpp b/src/utils/MeshTools.cpp index e946826..6bec61d 100644 --- a/src/utils/MeshTools.cpp +++ b/src/utils/MeshTools.cpp @@ -181,7 +181,7 @@ void reduceGrid(Mesh& m, const Grid& nG) m.grid = nG; - RedundancyCleaner::removeElementsWithCondition(m, [&](const Element&e) { + redundancyCleaner::removeElementsWithCondition(m, [&](const Element&e) { for (auto& vId : e.vertices) { Coordinate& c = m.coordinates[vId]; for (std::size_t d = 0; d < 3; d++) { @@ -195,7 +195,7 @@ void reduceGrid(Mesh& m, const Grid& nG) return false; }); - RedundancyCleaner::cleanCoords(m); + redundancyCleaner::cleanCoords(m); for (auto& c : m.coordinates) { c -= offset; } diff --git a/src/utils/RedundancyCleaner.cpp b/src/utils/RedundancyCleaner.cpp index e36161e..8873570 100644 --- a/src/utils/RedundancyCleaner.cpp +++ b/src/utils/RedundancyCleaner.cpp @@ -10,10 +10,9 @@ #include #include -namespace meshlib { -namespace utils { +namespace meshlib::utils::redundancyCleaner { -void RedundancyCleaner::removeRepeatedElementsIgnoringOrientation(Mesh& m) +void removeRepeatedElementsIgnoringOrientation(Mesh& m) { std::vector> toRemove(m.groups.size()); for (const auto& g : m.groups) { @@ -34,7 +33,7 @@ void RedundancyCleaner::removeRepeatedElementsIgnoringOrientation(Mesh& m) removeElements(m, toRemove); } -void RedundancyCleaner::removeRepeatedElements(Mesh& m) +void removeRepeatedElements(Mesh& m) { std::vector> toRemove(m.groups.size()); for (const auto& g : m.groups) { @@ -61,7 +60,7 @@ void RedundancyCleaner::removeRepeatedElements(Mesh& m) void getOverlappedDimensionZeroElementsAndIdenticalLines(const Group& group, std::set& overlappedElements); void getOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(const Group& group, const std::vector& meshCoordinates, std::set& overlappedElements); -void RedundancyCleaner::removeOverlappedDimensionZeroElementsAndIdenticalLines(Mesh & mesh) +void removeOverlappedDimensionZeroElementsAndIdenticalLines(Mesh & mesh) { std::vector> toRemove(mesh.groups.size()); @@ -73,7 +72,7 @@ void RedundancyCleaner::removeOverlappedDimensionZeroElementsAndIdenticalLines(M removeElements(mesh, toRemove); } -void RedundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(Mesh& mesh) +void removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(Mesh& mesh) { std::vector> toRemove(mesh.groups.size()); @@ -197,7 +196,7 @@ void getOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(const Group& } -void RedundancyCleaner::removeOverlappedElementsByDimension(Mesh& mesh, const std::vector& highestDimensions) +void removeOverlappedElementsByDimension(Mesh& mesh, const std::vector& highestDimensions) { std::vector> toRemove(mesh.groups.size()); @@ -218,7 +217,7 @@ void RedundancyCleaner::removeOverlappedElementsByDimension(Mesh& mesh, const st removeElements(mesh, toRemove); } -void RedundancyCleaner::removeElementsWithCondition(Mesh& m, std::function cnd) +void removeElementsWithCondition(Mesh& m, std::function cnd) { std::vector> toRemove(m.groups.size()); for (auto const& g : m.groups) { @@ -233,7 +232,7 @@ void RedundancyCleaner::removeElementsWithCondition(Mesh& m, std::function posIds; for (GroupId g = 0; g < mesh.groups.size(); g++) { @@ -275,13 +274,13 @@ void RedundancyCleaner::fuseCoords(Mesh& mesh) } } -void RedundancyCleaner::removeDegenerateElements(Mesh& mesh){ +void removeDegenerateElements(Mesh& mesh){ removeElementsWithCondition(mesh, [&](const Element& e) { return IdSet(e.vertices.begin(), e.vertices.end()).size() != e.vertices.size(); }); } -void RedundancyCleaner::cleanCoords(Mesh& output) +void cleanCoords(Mesh& output) { const std::size_t& numStrCoords = output.coordinates.size(); @@ -315,7 +314,7 @@ void RedundancyCleaner::cleanCoords(Mesh& output) } -void RedundancyCleaner::removeElements(Mesh& mesh, const std::vector& toRemove) +void removeElements(Mesh& mesh, const std::vector& toRemove) { for (GroupId gId = 0; gId < mesh.groups.size(); gId++) { Elements& elems = mesh.groups[gId].elements; @@ -336,4 +335,3 @@ void RedundancyCleaner::removeElements(Mesh& mesh, const std::vector& toR } } -} \ No newline at end of file diff --git a/src/utils/RedundancyCleaner.h b/src/utils/RedundancyCleaner.h index 9a91f40..2ff4c10 100644 --- a/src/utils/RedundancyCleaner.h +++ b/src/utils/RedundancyCleaner.h @@ -5,25 +5,22 @@ #include -namespace meshlib { -namespace utils { - -class RedundancyCleaner { -public: - static void cleanCoords(Mesh&); - static void fuseCoords(Mesh&); - static void removeDegenerateElements(Mesh&); - static void removeElementsWithCondition(Mesh&, std::function); - static void removeRepeatedElements(Mesh&); - static void removeRepeatedElementsIgnoringOrientation(Mesh&); - static void removeOverlappedDimensionZeroElementsAndIdenticalLines(Mesh&); - static void removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(Mesh&); - static void removeOverlappedElementsByDimension(Mesh&, const std::vector&); - static void removeElements(Mesh&, const std::vector&); -private: - static Elements findDegenerateElements_(const Group&, const Coordinates&); - }; +namespace meshlib::utils::redundancyCleaner { + +void cleanCoords(Mesh& mesh); +void fuseCoords(Mesh& mesh); + +void removeDegenerateElements(Mesh& mesh); + +void removeElementsWithCondition(Mesh& mesh, std::function condition); +void removeRepeatedElements(Mesh& mesh); +void removeRepeatedElementsIgnoringOrientation(Mesh& mesh); + +void removeOverlappedDimensionZeroElementsAndIdenticalLines(Mesh& mesh); +void removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(Mesh& mesh); +void removeOverlappedElementsByDimension(Mesh& mesh, const std::vector& dimension); + +void removeElements(Mesh& mesh, const std::vector& toRemove); -} } diff --git a/test/core/SlicerTest.cpp b/test/core/SlicerTest.cpp index 1ae8c6e..35cdf77 100644 --- a/test/core/SlicerTest.cpp +++ b/test/core/SlicerTest.cpp @@ -933,7 +933,7 @@ TEST_F(SlicerTest, canKeepOppositeLinesInLineDimensionPolicy) ASSERT_NO_THROW(resultMesh = Slicer(m, dimensions).getMesh()); EXPECT_FALSE(containsDegenerateTriangles(resultMesh)); - RedundancyCleaner::cleanCoords(resultMesh); + redundancyCleaner::cleanCoords(resultMesh); ASSERT_EQ(resultMesh.coordinates.size(), expectedCoordinates.size()); @@ -1080,4 +1080,116 @@ TEST_F(SlicerTest, sphere_case_patch_contour_check_2) vtkIO::exportMeshToVTU("contour.vtk", contourMesh); } +TEST_F(SlicerTest, canSliceTrianglesWithNullAreas) +{ + + // + // y y y + // *-------------*-------------* *-------------*-------------* *-------------*-------------* + // | 5---------ʌ---------6 | | 5---------ʌ---------6 | | 5---------ʌ---------6 | + // | | /|\ | | | | /|\0 | | | | /|\0 | | + // | | / | \ | | | | / |⎸\ | | | | / |⎸\ | | + // | | / |0 \ | | | | / || \ | | | | / || \ | | + // | | / | \ | | | | / | ⎸ \ | | | | / | ⎸ \ | | + // | | / | \ | | | | / | | \ | | | | / | | \ | | + // *---|---/-----*-----\---|---* -> *---|---/-----*--⎸--\---|---* -> *---*---*-----*--*--*---*---* + // | | / | \ | | | | / | ⎸ \ | | | | / | ⎸ \ | | + // | | / | 4 \ | | | | / | | \ | | | | / | | \ | | + // | |/________|________\| | | |/________|__|_____\| | | |/________*__|_____\| | + // | 1‾==‾‾‾‾‾|‾‾/‾‾‾‾=‾2 | | 1‾-_ | /4 -‾2 | | 1‾-_ | /4 -‾2 | + // | ‾-_ | / _-‾ | | ‾-_ | / _-‾ | | ‾-_ | / _-‾ | + // | ‾-|/_-‾ | | ‾-|/_-‾ | | ‾-|/_-‾ | + // *-------------3-------------* x *-------------3-------------* x *-------------3-------------* x + + Mesh m; + m.grid = { + std::vector({-5.0, 0.0, 5.0}), + std::vector({-5.0, 0.0, 5.0}), + std::vector({-5.0, 0.0, 5.0}) + }; + Relatives relatives = { + Relative({ 0.0, 1.5, 0.0 }), // 0 + Relative({ 0.0, 0.0, 0.0 }), // 1 + Relative({ 0.0, 0.0, 0.0 }), // 2 + Relative({ 0.0, 0.0, 0.0 }), // 3 + Relative({ 0.0, 0.0, 0.0 }), // 4 + Relative({ 0.0, 0.0, 0.0 }), // 5 + Relative({ 0.0, 0.0, 0.0 }), // 6 + }; + m.groups.resize(2); + m.groups[0].elements = { + Element{ {0, 1}, Element::Type::Line } + }; + m.groups[1].elements = { + Element{ {2, 3}, Element::Type::Line } + }; + GridTools tools(m.grid); + + Coordinate intersectionPointFirstSegment = Coordinate({ 0.0, -5.0, -5.0 }); + Coordinate intersectionPointSecondSegment = Coordinate({ 0.0, -5.0, 0.0 }); + + + + Coordinates expectedCoordinates = { + m.coordinates[0], // 0 First Segment, First Point + intersectionPointFirstSegment, // 1 First Segment, Intersection Point + m.coordinates[1], // 2 First Segment, Final Point + m.coordinates[2], // 3 Second Segment, First Point + intersectionPointSecondSegment, // 4 Second Segment, Intersection Point + m.coordinates[3], // 5 Second Segment, Final Point + }; + + Relatives expectedRelatives = tools.absoluteToRelative(expectedCoordinates); + + std::vector expectedElements = { + { + Element({0, 1}, Element::Type::Line), + Element({1, 2}, Element::Type::Line), + }, + { + Element({3, 4}, Element::Type::Line), + Element({4, 5}, Element::Type::Line), + }, + }; + + Mesh resultMesh; + ASSERT_NO_THROW(resultMesh = Slicer{ m }.getMesh()); + + EXPECT_FALSE(containsDegenerateTriangles(resultMesh)); + + ASSERT_EQ(resultMesh.coordinates.size(), expectedCoordinates.size()); + ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + + ASSERT_EQ(resultMesh.groups[0].elements.size(), 2); + ASSERT_EQ(resultMesh.groups[1].elements.size(), 2); + + for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { + for (std::size_t axis = 0; axis < 3; ++axis) { + EXPECT_DOUBLE_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]) + << "Current coordinate: #" << i << std::endl + << "Current Axis: #" << axis << std::endl; + } + } + + for (std::size_t g = 0; g < expectedElements.size(); ++g) { + auto& resultGroup = resultMesh.groups[g]; + auto& expectedGroup = expectedElements[g]; + + EXPECT_TRUE(resultGroup.elements[0].isLine()); + EXPECT_TRUE(resultGroup.elements[1].isLine()); + + for (std::size_t e = 0; e < expectedGroup.size(); ++e) { + auto& resultElement = resultGroup.elements[e]; + auto& expectedElement = expectedGroup[e]; + + for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { + EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]) + << "Current Group: #" << g << std::endl + << "Current Element: #" << e << std::endl + << "Current Vertex: #" << v << std::endl; + } + } + } +} + } \ No newline at end of file diff --git a/test/meshers/StaircaseMesherTest.cpp b/test/meshers/StaircaseMesherTest.cpp index 53d7373..c9bd487 100644 --- a/test/meshers/StaircaseMesherTest.cpp +++ b/test/meshers/StaircaseMesherTest.cpp @@ -267,7 +267,7 @@ TEST_F(StaircaseMesherTest, DISABLED_visualSelectiveStaircaserCone) auto resultMesh = meshlib::core::Staircaser{ collapsedMesh }.getSelectiveMesh(cellSet); // ASSERT_NO_THROW(meshTools::checkNoCellsAreCrossed(resultMesh)); - RedundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(resultMesh); + redundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(resultMesh); utils::meshTools::reduceGrid(resultMesh, inputMesh.grid); utils::meshTools::convertToAbsoluteCoordinates(resultMesh); @@ -386,7 +386,7 @@ TEST_F(StaircaseMesherTest, selectiveStaircaser_preserves_topological_closedness auto resultMesh = staircaser.getSelectiveMesh(cellSet, meshlib::core::Staircaser::GapsFillingType::Insert); - RedundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(resultMesh); + redundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(resultMesh); utils::meshTools::reduceGrid(resultMesh, mesh.grid); utils::meshTools::convertToAbsoluteCoordinates(resultMesh); @@ -435,7 +435,7 @@ TEST_F(StaircaseMesherTest, selectiveStaircaser_preserves_topological_closedness auto resultMesh = meshlib::core::Staircaser{ collapsedMesh }.getSelectiveMesh(cellSet); - RedundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(resultMesh); + redundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(resultMesh); utils::meshTools::reduceGrid(resultMesh, mesh.grid); utils::meshTools::convertToAbsoluteCoordinates(resultMesh); @@ -486,7 +486,7 @@ TEST_F(StaircaseMesherTest, staircaser_reads_wires_correctly) auto resultMesh = meshlib::core::Staircaser{ collapsedMesh }.getMesh(); - RedundancyCleaner::removeOverlappedElementsByDimension(resultMesh, dimensions); + redundancyCleaner::removeOverlappedElementsByDimension(resultMesh, dimensions); utils::meshTools::reduceGrid(resultMesh, mesh.grid); utils::meshTools::convertToAbsoluteCoordinates(resultMesh); diff --git a/test/utils/RedundancyCleanerTest.cpp b/test/utils/RedundancyCleanerTest.cpp index 25e1dcb..94b70ac 100644 --- a/test/utils/RedundancyCleanerTest.cpp +++ b/test/utils/RedundancyCleanerTest.cpp @@ -24,7 +24,7 @@ TEST_F(RedundancyCleanerTest, removeRepeatedElements) auto r{ m }; r.groups[0].elements.push_back(m.groups[0].elements.back()); - RedundancyCleaner::removeRepeatedElements(r); + redundancyCleaner::removeRepeatedElements(r); EXPECT_EQ(m, r); } @@ -38,7 +38,7 @@ TEST_F(RedundancyCleanerTest, removeRepeatedElements_with_indices_rotated) auto& e = r.groups[0].elements.back(); std::rotate(e.vertices.begin(), e.vertices.begin() + 1, e.vertices.end()); - RedundancyCleaner::removeRepeatedElements(r); + redundancyCleaner::removeRepeatedElements(r); EXPECT_EQ(m, r); } @@ -60,7 +60,7 @@ TEST_F(RedundancyCleanerTest, removeRepeatedLinesFromSameGroup) Element({0, 1}, Element::Type::Line), }; - RedundancyCleaner::removeRepeatedElements(m); + redundancyCleaner::removeRepeatedElements(m); EXPECT_EQ(m.coordinates.size(), 2); EXPECT_EQ(m.groups.size(), 2); @@ -129,7 +129,7 @@ TEST_F(RedundancyCleanerTest, removeOverlappedElementsContainedWithinLines) } }; - RedundancyCleaner::removeOverlappedDimensionZeroElementsAndIdenticalLines(m); + redundancyCleaner::removeOverlappedDimensionZeroElementsAndIdenticalLines(m); EXPECT_EQ(m.coordinates.size(), expectedCoordinates.size()); ASSERT_EQ(m.groups.size(), expectedElementsList.size()); @@ -299,7 +299,7 @@ TEST_F(RedundancyCleanerTest, removeOverlappedElementsWhenSurfaceMeshing) } }; - RedundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(m); + redundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(m); EXPECT_EQ(m.coordinates.size(), expectedCoordinates.size()); ASSERT_EQ(m.groups.size(), expectedElementsList.size()); @@ -603,7 +603,7 @@ TEST_F(RedundancyCleanerTest, removeOverlappedElementsbyDimension) { }, }; - RedundancyCleaner::removeOverlappedElementsByDimension(mesh, dimensions); + redundancyCleaner::removeOverlappedElementsByDimension(mesh, dimensions); EXPECT_EQ(mesh.coordinates.size(), expectedCoordinates.size()); ASSERT_EQ(mesh.groups.size(), expectedElementsList.size()); @@ -660,7 +660,7 @@ TEST_F(RedundancyCleanerTest, doNotRemoveOppositeLines) auto resultMesh{ m }; - RedundancyCleaner::removeRepeatedElements(resultMesh); + redundancyCleaner::removeRepeatedElements(resultMesh); EXPECT_EQ(resultMesh, m); } @@ -685,7 +685,7 @@ TEST_F(RedundancyCleanerTest, removeElementsWithCondition) { Mesh r = m; - RedundancyCleaner::removeElementsWithCondition(r, [](auto e) {return e.isNone(); }); + redundancyCleaner::removeElementsWithCondition(r, [](auto e) {return e.isNone(); }); EXPECT_EQ(3, m.groups[0].elements.size()); EXPECT_EQ(2, r.groups[0].elements.size()); @@ -694,7 +694,7 @@ TEST_F(RedundancyCleanerTest, removeElementsWithCondition) { Mesh r = m; - RedundancyCleaner::removeElementsWithCondition( + redundancyCleaner::removeElementsWithCondition( r, [](auto e) {return e.isLine() || e.isNone(); }); EXPECT_EQ(3, m.groups[0].elements.size()); From 173f6c7b1c9460aee70ee37179d7b40ac37743c0 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Thu, 20 Nov 2025 12:11:30 +0100 Subject: [PATCH 05/35] WIP | DRAFT | Checking overlapped triangles in conformal --- src/meshers/ConformalMesher.cpp | 34 +++++ src/meshers/ConformalMesher.h | 1 + test/meshers/ConformalMesherTest.cpp | 198 ++++++++++++++++++++++++++- 3 files changed, 232 insertions(+), 1 deletion(-) diff --git a/src/meshers/ConformalMesher.cpp b/src/meshers/ConformalMesher.cpp index 06490b8..3bb84cf 100644 --- a/src/meshers/ConformalMesher.cpp +++ b/src/meshers/ConformalMesher.cpp @@ -10,6 +10,7 @@ #include "utils/GridTools.h" #include "utils/MeshTools.h" #include "utils/CoordGraph.h" +#include "utils/ElemGraph.h" #include "utils/RedundancyCleaner.h" namespace meshlib::meshers { @@ -130,6 +131,36 @@ std::set ConformalMesher::cellsWithMoreThanAPathPerFace(const Mesh& mesh) return res; } +std::set ConformalMesher::cellsWithOverlappingTriangles(const Mesh& mesh) { + double angle = 179.9999; + + std::set cellsWithOverlap; + GridTools gridTools(mesh.grid); + for (const auto& g : mesh.groups) { + ElemGraph eG(g.elements, mesh.coordinates); + for (const auto& elPair : eG.findElementsWithWeight(angle)) { + const Element& element1 = g.elements[elPair.first]; + const Element& element2 = g.elements[elPair.second]; + + Element element2Copy(element2); + + auto firstCommonPointPosition = std::find(element2Copy.vertices.begin(), element2Copy.vertices.end(), element1.vertices.front()); + + std::rotate(element2Copy.vertices.begin(), firstCommonPointPosition, element2Copy.vertices.end()); + + if (element1.vertices[0] == element2Copy.vertices[0] && + element1.vertices[1] == element2Copy.vertices[2] && + element1.vertices[2] == element2Copy.vertices[1]) { + continue; + } + + Relative innerRelative = mesh.coordinates[element1.vertices[0]]; + cellsWithOverlap.insert(gridTools.toCell(innerRelative)); + } + } + return cellsWithOverlap; +} + std::set ConformalMesher::cellsWithAVertexInAnEdgeForbiddenRegion(const Mesh& mesh) { std::set res; @@ -156,6 +187,9 @@ std::set ConformalMesher::findNonConformalCells(const Mesh& mesh) // Rule #2: Cell faces must always be crossed by a single path. res = mergeCellSets(res, cellsWithMoreThanAPathPerFace(mesh)); + // Rule #X Cell faces must not have overlapping triangles. + res = mergeCellSets(res, cellsWithOverlappingTriangles(mesh)); + // Rule #3: Conformal cells can't contain node or line elements. // res = mergeCellSets(res, cellsContainingNodeOrLineElements(mesh)); diff --git a/src/meshers/ConformalMesher.h b/src/meshers/ConformalMesher.h index 8fe5540..6380ff5 100644 --- a/src/meshers/ConformalMesher.h +++ b/src/meshers/ConformalMesher.h @@ -23,6 +23,7 @@ class ConformalMesher : public MesherBase { static std::set cellsWithMoreThanAPathPerFace(const Mesh& mesh); static std::set cellsWithInteriorDisconnectedPatches(const Mesh& mesh); static std::set cellsWithAVertexInAnEdgeForbiddenRegion(const Mesh& mesh); + static std::set cellsWithOverlappingTriangles(const Mesh& mesh); private: Mesh inputMesh_; ConformalMesherOptions opts_; diff --git a/test/meshers/ConformalMesherTest.cpp b/test/meshers/ConformalMesherTest.cpp index bbc52a7..f39d57f 100644 --- a/test/meshers/ConformalMesherTest.cpp +++ b/test/meshers/ConformalMesherTest.cpp @@ -1,4 +1,4 @@ -#include "gtest/gtest.h" +#include "gtest/gtest.h" #include "MeshFixtures.h" #include "MeshTools.h" @@ -333,6 +333,202 @@ TEST_F(ConformalMesherTest, cellsWithMoreThanAPathPerFace_8) EXPECT_EQ(2, res.size()); } +TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_1) +{ + // 2 Surface Triangles with common edge on cell line + // 1_ + // ║\‾-_ + // ║ \ ‾-_ + // ║ \ ‾-_v + // 0===2------3 + Mesh m; + { + m.grid = buildUnitLengthGrid(0.1); // 10 x 10 x 10 grid + m.coordinates = { + Relative({0.0, 0.0, 1.0}), // 0 + Relative({0.0, 1.0, 1.0}), // 1 + Relative({0.4, 0.0, 1.0}), // 2 + Relative({1.0, 0.0, 1.0}), // 3 + }; + m.groups = { Group() }; + m.groups[0].elements = { + Element({0, 1, 2}), + Element({3, 1, 0}), + }; + } + + auto res = ConformalMesher::cellsWithOverlappingTriangles(m); + + EXPECT_EQ(1, res.size()); +} + +TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_2) +{ + // 2 Surface Triangles with common edge on surface diagonal + // 1 + // ⎹=_ + // ⎹\‾=_ + // ⎹ \ ‾=_ + // ⎹ \ ‾= + // 0---2===3 + Mesh m; + { + m.grid = buildUnitLengthGrid(0.1); // 10 x 10 x 10 grid + m.coordinates = { + Relative({0.0, 0.0, 1.0}), // 0 + Relative({0.0, 1.0, 1.0}), // 1 + Relative({0.5, 0.0, 1.0}), // 2 + Relative({1.0, 0.0, 1.0}), // 3 + }; + m.groups = { Group() }; + m.groups[0].elements = { + Element({0, 1, 3}), + Element({2, 3, 1}), + }; + } + + auto res = ConformalMesher::cellsWithOverlappingTriangles(m); + + EXPECT_EQ(1, res.size()); +} + +TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_3) +{ + // 2 Diagonal Triangles with common edge in Cell surface + // *--------------3 + // /| ⫽║ + // / | ╱//║⎸ + // z/ | ╱ //║ ⎸ + // *---┼------⌿--*//║ | + // | |y ╱ /⎹║ ⎸ + // | *--╱-----⌿-┼║--* + // | / ╱ / ⎹║ / + // | /╱ / ⎹║ / + // |⫽ / ║/ + // 0--------1====2 x + + Mesh m; + { + m.grid = buildUnitLengthGrid(0.1); // 10 x 10 x 10 grid + m.coordinates = { + Relative({0.0, 0.0, 1.0}), // 0 + Relative({0.6, 0.0, 1.0}), // 1 + Relative({1.0, 0.0, 1.0}), // 2 + Relative({1.0, 1.0, 2.0}), // 3 + }; + m.groups = { Group() }; + m.groups[0].elements = { + Element({1, 2, 3}), // 0 + Element({3, 2, 0}), // 1 + }; + } + + auto res = ConformalMesher::cellsWithOverlappingTriangles(m); + + EXPECT_EQ(1, res.size()); +} + +TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_4) +{ + // 2 Diagonal Triangles with common edge in Cell diagonal + // *--------------3 + // /| ⫽⎹⎸ + // / | ⫽ /|⎸ + // z/ | ⫽ /⎹ ⎸ + // *---┼------⫽--*/ | ⎸ + // | |y ⫽ | | ⎸ + // | *--⫽-----⌿┼-┼--* + // | / ⫽ / |⎹ / + // | /⫽ / |⎸ / + // |⫽ / ║/ + // 0========1----2 x + + Mesh m; + { + m.grid = buildUnitLengthGrid(0.1); // 10 x 10 x 10 grid + m.coordinates = { + Relative({0.0, 0.0, 1.0}), // 0 + Relative({0.0, 1.0, 1.0}), // 1 + Relative({0.4, 0.0, 1.0}), // 2 + Relative({1.0, 0.0, 1.0}), // 3 + Relative({1.0, 1.0, 1.0}), // 4 + }; + m.groups = { Group() }; + m.groups[0].elements = { + Element({0, 1, 2}), + Element({4, 3, 0}), + }; + } + + auto res = ConformalMesher::cellsWithOverlappingTriangles(m); + + EXPECT_EQ(1, res.size()); +} + +TEST_F(ConformalMesherTest, allow_identical_triangles_with_opposite_normals_1) +{ + // 2 identical triangles in the same surface + // + // 1_ + // ║‾=_ + // ║ ‾=_ + // ║ ‾=_ + // 0=======2 + Mesh m; + { + m.grid = buildUnitLengthGrid(0.1); // 10 x 10 x 10 grid + m.coordinates = { + Relative({0.0, 0.0, 1.0}), // 0 + Relative({0.0, 1.0, 1.0}), // 1 + Relative({0.1, 0.0, 1.0}), // 2 + }; + m.groups = { Group() }; + m.groups[0].elements = { + Element({0, 1, 2}), + Element({1, 0, 2}), + }; + } + + auto res = ConformalMesher::cellsWithOverlappingTriangles(m); + + EXPECT_EQ(0, res.size()); +} + +TEST_F(ConformalMesherTest, allow_identical_triangles_with_opposite_normals_2) +{ + // 2 Diagonal Triangles with common edge in Cell surface + // *---------------2 + // /| ⫽/⎹⎸ + // / | ⫽ /⎹⎸| + // z/ | ⫽ / ⎹⎸⎸ + // *---┼------⫽---* ║ ⎸ + // | |y ⫽ | ⎹⎸ ⎸ + // | *--⫽-------┼-║-* + // | / ⫽ |⎹⎸/ + // | /⫽ |║/ + // |⫽ ║/ + // 0===============2 x + + Mesh m; + { + m.grid = buildUnitLengthGrid(0.1); // 10 x 10 x 10 grid + m.coordinates = { + Relative({0.0, 0.0, 1.0}), // 0 + Relative({1.0, 0.0, 1.0}), // 1 + Relative({1.0, 1.0, 2.0}), // 2 + }; + m.groups = { Group() }; + m.groups[0].elements = { + Element({1, 0, 2}), // 0 + Element({0, 1, 2}), // 1 + }; + } + + auto res = ConformalMesher::cellsWithOverlappingTriangles(m); + + EXPECT_EQ(0, res.size()); +} + TEST_F(ConformalMesherTest, sphere) { // Input From 4dd5420fe0952dc50941b76dedf1608fad7e2d19 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Thu, 20 Nov 2025 17:23:35 +0100 Subject: [PATCH 06/35] WIP | DRAFT | fixing wrong orientation in selective staircaser --- src/core/Staircaser.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/core/Staircaser.cpp b/src/core/Staircaser.cpp index 5b3bddf..e54555e 100644 --- a/src/core/Staircaser.cpp +++ b/src/core/Staircaser.cpp @@ -252,22 +252,12 @@ void Staircaser::fillGaps(const RelativePairSet boundaryCoordinatePairs) { thirdVertex = -1; const auto& verts = elem.vertices; - auto it1 = std::find(verts.begin(), verts.end(), v1); - auto it2 = std::find(verts.begin(), verts.end(), v2); + auto itV1 = std::find(verts.begin(), verts.end(), v1); + auto itV2 = std::find(verts.begin(), verts.end(), v2); - int idx1 = std::distance(verts.begin(), it1); - int idx2 = std::distance(verts.begin(), it2); - - int nextIdx1 = (idx1 + 1) % 3; - int prevIdx1 = (idx1 + 2) % 3; - - if (idx2 == nextIdx1) { - thirdVertex = verts[(idx2 + 1) % 3]; - correctOrientation = true; - } else if (idx2 == prevIdx1) { - thirdVertex = verts[(idx1 + 1) % 3]; - correctOrientation = false; - } + auto idx1 = static_cast(std::distance(verts.begin(), itV1)); + auto idx2 = static_cast(std::distance(verts.begin(), itV2)); + correctOrientation = (idx2 == (idx1 + 1) % 3); } } From 49d23ee705c24c8d19f3cca99881a620b6efab48 Mon Sep 17 00:00:00 2001 From: ashybabashyba Date: Fri, 21 Nov 2025 10:11:32 +0100 Subject: [PATCH 07/35] Fix triangle orientation check in splitLinesWithNeighborTriangle --- src/core/Staircaser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/Staircaser.cpp b/src/core/Staircaser.cpp index e54555e..b64d55e 100644 --- a/src/core/Staircaser.cpp +++ b/src/core/Staircaser.cpp @@ -513,7 +513,9 @@ void Staircaser::splitLinesWithNeighborTriangle(const size_t groupIndex, std::se auto itV1 = std::find(e.vertices.begin(), e.vertices.end(), v1); auto itV2 = std::find(e.vertices.begin(), e.vertices.end(), v2); - correctOrientation = (itV1 < itV2); + auto idx1 = static_cast(std::distance(e.vertices.begin(), itV1)); + auto idx2 = static_cast(std::distance(e.vertices.begin(), itV2)); + correctOrientation = (idx2 == (idx1 + 1) % 3); if(!correctOrientation) { std::swap(v1, v2); From d64f024e390c30af09ab44979a621b9c00381120 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Fri, 21 Nov 2025 13:17:17 +0100 Subject: [PATCH 08/35] WIP | DRAFT | fixing wrong orientation in selective staircaser --- src/meshers/ConformalMesher.cpp | 22 +++++- test/meshers/ConformalMesherTest.cpp | 105 +++++++++++++++++++++++---- 2 files changed, 110 insertions(+), 17 deletions(-) diff --git a/src/meshers/ConformalMesher.cpp b/src/meshers/ConformalMesher.cpp index 3bb84cf..a222613 100644 --- a/src/meshers/ConformalMesher.cpp +++ b/src/meshers/ConformalMesher.cpp @@ -153,9 +153,23 @@ std::set ConformalMesher::cellsWithOverlappingTriangles(const Mesh& mesh) element1.vertices[2] == element2Copy.vertices[1]) { continue; } - - Relative innerRelative = mesh.coordinates[element1.vertices[0]]; - cellsWithOverlap.insert(gridTools.toCell(innerRelative)); + auto triv1 = Geometry::asTriV(element1, mesh.coordinates); + auto triv2 = Geometry::asTriV(element2, mesh.coordinates); + /**/ + auto lowestRelativeIt1 = std::min_element(triv1.begin(), triv1.end()); + auto lowestRelativeIt2 = std::min_element(triv2.begin(), triv2.end()); + + cellsWithOverlap.insert(gridTools.toCell(std::min(*lowestRelativeIt1, *lowestRelativeIt2))); + + /* + + for (const auto& coordinate : triv1) { + cellsWithOverlap.insert(gridTools.toCell(coordinate)); + } + for (const auto& coordinate : triv2) { + cellsWithOverlap.insert(gridTools.toCell(coordinate)); + } + /**/ } } return cellsWithOverlap; @@ -235,7 +249,7 @@ Mesh ConformalMesher::mesh() const logNumberOfTriangles(countMeshElementsIf(res, isTriangle)); // Find cells which break conformal FDTD rules after selective structuring. - nonConformalCells = findNonConformalCells(res); + // nonConformalCells = findNonConformalCells(res); log("Non-conformal cells found after Selective Structuring: " + std::to_string(nonConformalCells.size()), 1); diff --git a/test/meshers/ConformalMesherTest.cpp b/test/meshers/ConformalMesherTest.cpp index f39d57f..8cca7d2 100644 --- a/test/meshers/ConformalMesherTest.cpp +++ b/test/meshers/ConformalMesherTest.cpp @@ -17,9 +17,9 @@ class ConformalMesherTest : public ::testing::Test { Mesh launchConformalMesher(const std::string& inputFilename, const Mesh& inputMesh) { ConformalMesherOptions opts; - opts.snapperOptions.edgePoints = 3; - opts.snapperOptions.forbiddenLength = 0.3; - + opts.snapperOptions.edgePoints = 20; + opts.snapperOptions.forbiddenLength = 0.005; + ConformalMesher mesher{inputMesh, opts}; Mesh res = mesher.mesh(); @@ -359,7 +359,13 @@ TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_1) auto res = ConformalMesher::cellsWithOverlappingTriangles(m); - EXPECT_EQ(1, res.size()); + Cell expectedCell = Cell({ 0, 0, 1 }); + + ASSERT_EQ(1, res.size()); + for (Axis axis = X; axis <= Z; ++axis) { + const Cell& cell = *res.begin(); + EXPECT_EQ(expectedCell[axis], cell[axis]) << "Cell Axis #" << axis; + } } TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_2) @@ -390,6 +396,14 @@ TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_2) auto res = ConformalMesher::cellsWithOverlappingTriangles(m); EXPECT_EQ(1, res.size()); + + Cell expectedCell = Cell({ 0, 0, 1 }); + + ASSERT_EQ(1, res.size()); + for (Axis axis = X; axis <= Z; ++axis) { + const Cell& cell = *res.begin(); + EXPECT_EQ(expectedCell[axis], cell[axis]) << "Cell Axis #" << axis; + } } TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_3) @@ -426,6 +440,14 @@ TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_3) auto res = ConformalMesher::cellsWithOverlappingTriangles(m); EXPECT_EQ(1, res.size()); + + Cell expectedCell = Cell({ 0, 0, 1 }); + + ASSERT_EQ(1, res.size()); + for (Axis axis = X; axis <= Z; ++axis) { + const Cell& cell = *res.begin(); + EXPECT_EQ(expectedCell[axis], cell[axis]) << "Cell Axis #" << axis; + } } TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_4) @@ -448,21 +470,72 @@ TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_4) m.grid = buildUnitLengthGrid(0.1); // 10 x 10 x 10 grid m.coordinates = { Relative({0.0, 0.0, 1.0}), // 0 - Relative({0.0, 1.0, 1.0}), // 1 - Relative({0.4, 0.0, 1.0}), // 2 - Relative({1.0, 0.0, 1.0}), // 3 - Relative({1.0, 1.0, 1.0}), // 4 + Relative({0.4, 0.0, 1.0}), // 1 + Relative({1.0, 0.0, 1.0}), // 2 + Relative({1.0, 1.0, 1.0}), // 3 }; m.groups = { Group() }; m.groups[0].elements = { - Element({0, 1, 2}), - Element({4, 3, 0}), + Element({0, 2, 3}), + Element({3, 1, 0}), + }; + } + + auto res = ConformalMesher::cellsWithOverlappingTriangles(m); + + ASSERT_EQ(1, res.size()); + + Cell expectedCell = Cell({ 0, 0, 1 }); + + EXPECT_EQ(1, res.size()); + for (Axis axis = X; axis <= Z; ++axis) { + const Cell& cell = *res.begin(); + EXPECT_EQ(expectedCell[axis], cell[axis]) << "Cell Axis #" << axis; + } +} + +TEST_F(ConformalMesherTest, cellsWithOverlappingTriangles_5) +{ + // 2 Diagonal Triangles with common edge in Cell diagonal + // *--------------3 + // /| /| + // / | / | + // z/ | / | + // *---┼----------0 | + // | |y _-‾| | + // | *----_-‾---┼---* + // | / -‾ _--1 / + // | / _-‾__-‾‾ ║ / + // |/==‾‾‾ ║/ + // 3==============2 x + + Mesh m; + { + m.grid = buildUnitLengthGrid(0.1); // 10 x 10 x 10 grid + m.coordinates = { + Relative({1.0, 0.0, 2.0}), // 0 + Relative({1.0, 0.0, 1.6}), // 1 + Relative({1.0, 0.0, 1.0}), // 2 + Relative({0.0, 0.0, 1.0}), // 3 + }; + m.groups = { Group() }; + m.groups[0].elements = { + Element({0, 2, 3}), // 0 + Element({2, 1, 3}), // 1 }; } auto res = ConformalMesher::cellsWithOverlappingTriangles(m); EXPECT_EQ(1, res.size()); + + Cell expectedCell = Cell({ 0, 0, 1 }); + + ASSERT_EQ(1, res.size()); + for (Axis axis = X; axis <= Z; ++axis) { + const Cell& cell = *res.begin(); + EXPECT_EQ(expectedCell[axis], cell[axis]) << "Cell Axis #" << axis; + } } TEST_F(ConformalMesherTest, allow_identical_triangles_with_opposite_normals_1) @@ -563,15 +636,22 @@ TEST_F(ConformalMesherTest, alhambra) // Input const std::string inputFilename = "testData/cases/alhambra/alhambra.stl"; auto inputMesh = vtkIO::readInputMesh(inputFilename); + /* + inputMesh.grid[X] = utils::GridTools::linspace(-4.0, -2.0, 2); // rel: 25 -> 35 (10 cells/11 planes) + inputMesh.grid[Y] = utils::GridTools::linspace(36.0, 38.0, 2); // rel: 20.0 -> 40.0 | 40 -> 50 (10 cells/11 planes) + inputMesh.grid[Z] = utils::GridTools::linspace(7.491016, 9.36367, 2); // 0 -> 1.872654 + /**/ inputMesh.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 61); inputMesh.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 61); inputMesh.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 8); - + /**/ // Mesh auto mesh = launchConformalMesher(inputFilename, inputMesh); + // checkNoOverlaps(mesh); // For Debugging. + /**/ mesh.coordinates = utils::GridTools{mesh.grid}.absoluteToRelative(mesh.coordinates); { auto cells = ConformalMesher::cellsWithMoreThanAVertexInsideEdge(mesh); @@ -585,6 +665,7 @@ TEST_F(ConformalMesherTest, alhambra) utils::meshTools::convertToAbsoluteCoordinates(dbgMesh); exportMeshToVTU("testData/cases/alhambra/alhambra.breaksRuleNo2.vtk", dbgMesh); } + /**/ } TEST_F(ConformalMesherTest, cone) @@ -671,6 +752,4 @@ TEST_F(ConformalMesherTest, thinCylinder) // EXPECT_EQ(4, countMeshElementsIf(p, isTriangle)); // } - - } \ No newline at end of file From dcbb720bc0783a13f83da727c91586d8006824eb Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Mon, 24 Nov 2025 17:33:01 +0100 Subject: [PATCH 09/35] WIP | DRAFT | fixing errors in original selective staircasing --- src/core/Staircaser.cpp | 398 +++++++------- src/core/Staircaser.h | 4 +- test/core/StaircaserTest.cpp | 992 +++++++++-------------------------- 3 files changed, 439 insertions(+), 955 deletions(-) diff --git a/src/core/Staircaser.cpp b/src/core/Staircaser.cpp index b64d55e..f3c3baf 100644 --- a/src/core/Staircaser.cpp +++ b/src/core/Staircaser.cpp @@ -8,7 +8,7 @@ namespace meshlib { namespace core { using namespace utils; -Staircaser::Staircaser(const Mesh& inputMesh) : GridTools(inputMesh.grid) +Staircaser::Staircaser(const Mesh& inputMesh) : GridTools(inputMesh.grid), fillerType_(GapsFillingType::None) { inputMesh_ = inputMesh; @@ -48,21 +48,21 @@ Mesh Staircaser::getMesh() return mesh_; } -CoordinateMap buildCoordinateMap(const Coordinates& cs) +CoordinateMap buildCoordinateMap(const Coordinates& coordinates) { - CoordinateMap res; - for (std::size_t c = 0; c < cs.size(); ++c) { - res[cs[c]] = c; + CoordinateMap result; + for (CoordinateId c = 0; c < coordinates.size(); ++c) { + result[coordinates[c]] = c; } - return res; + return result; } IdSet findCommonNeighborsVertices(const Mesh& mesh, const std::pair& edge) { IdSet commonNeighborsVertices; - const auto& vertex1 = edge.first; - const auto& vertex2 = edge.second; + const CoordinateId& vertex1 = edge.first; + const CoordinateId& vertex2 = edge.second; GridTools gridTools; auto cellElemMap = gridTools.buildCellElemMap(mesh.groups[0].elements, mesh.coordinates); @@ -74,12 +74,12 @@ IdSet findCommonNeighborsVertices(const Mesh& mesh, const std::pairvertices; auto it = std::find(elementVertices.begin(), elementVertices.end(), vertex1); @@ -94,12 +94,12 @@ IdSet findCommonNeighborsVertices(const Mesh& mesh, const std::pairvertices; auto it = std::find(elementVertices.begin(), elementVertices.end(), vertex2); @@ -114,27 +114,28 @@ IdSet findCommonNeighborsVertices(const Mesh& mesh, const std::pair& edge) { - Elements foundTriangles; - const auto& v1 = edge.first; - const auto& v2 = edge.second; +std::vector findTrianglesWithEdge(const Elements& elements, const std::pair& edge) { + std::vector foundTriangles; + const CoordinateId& vertex1 = edge.first; + const CoordinateId& vertex2 = edge.second; - for (const auto& element : mesh.groups[0].elements) { + for (ElementId e = 0; e < elements.size(); ++e) { + const Element& element = elements[e]; if (element.vertices.size() == 3) { - auto it1 = std::find(element.vertices.begin(), element.vertices.end(), v1); - auto it2 = std::find(element.vertices.begin(), element.vertices.end(), v2); + auto it1 = std::find(element.vertices.begin(), element.vertices.end(), vertex1); + auto it2 = std::find(element.vertices.begin(), element.vertices.end(), vertex2); if (it1 != element.vertices.end() && it2 != element.vertices.end()) { - foundTriangles.push_back(element); + foundTriangles.push_back(e); } } } @@ -153,13 +154,13 @@ Mesh Staircaser::getSelectiveMesh(const std::set& cellsToStructure, GapsFi auto& meshGroup = mesh_.groups[g]; meshGroup.elements.reserve(inputGroup.elements.size() * 2); - auto cellElemMap = buildCellElemMap(inputGroup.elements, inputMesh_.coordinates); + auto inputCellElemMap = buildCellElemMap(inputGroup.elements, inputMesh_.coordinates); - for (const auto& c : cellsToStructure) { - if (!cellElemMap.count(c)) { + for (const auto& cell : cellsToStructure) { + if (!inputCellElemMap.count(cell)) { continue; } - for (const auto e: cellElemMap.at(c)) { + for (const auto e: inputCellElemMap.at(cell)) { if (e->isLine()) { this->processLineAndAddToGroup(*e, inputMesh_.coordinates, mesh_.coordinates, meshGroup); } @@ -173,14 +174,14 @@ Mesh Staircaser::getSelectiveMesh(const std::set& cellsToStructure, GapsFi std::map> cellElemMap_withNewElements; CoordinateMap coordinateMap = buildCoordinateMap(mesh_.coordinates); - for (const auto& [cell, elements] : cellElemMap) { + for (const auto& [cell, elements] : inputCellElemMap) { if (cellsToStructure.count(cell) ) { continue; } - for (const auto e : elements) { - auto [newElement, boundaryCoordinates] = obtainNewIndexForElement(*e, cellsToStructure, coordinateMap); + for (const auto elementIt : elements) { + auto [newElement, boundaryCoordinates] = obtainNewIndexForElement(*elementIt, cellsToStructure, coordinateMap); - auto allCoordsOnBoundary = isAllCoordinatesOnTheSameCellBoundary(newElement, cellsToStructure); + bool allCoordsOnBoundary = isAllCoordinatesOnTheSameCellBoundary(newElement, cellsToStructure); if (!allCoordsOnBoundary) { meshGroup.elements.push_back(newElement); @@ -197,7 +198,7 @@ Mesh Staircaser::getSelectiveMesh(const std::set& cellsToStructure, GapsFi auto itCell = cellElemMap_withNewElements.find(cell); if (itCell != cellElemMap_withNewElements.end()) { - auto elementsConvertedInLines = getElementsConvertedInLines(cell, cellElemMap_withNewElements); + auto elementsConvertedInLines = getElementsConvertedToLines(cell, cellElemMap_withNewElements); splitLinesWithNeighborTriangle(g, elementsConvertedInLines, meshGroup, cell, cellElemMap_withNewElements, toRemove); } @@ -212,9 +213,9 @@ Mesh Staircaser::getSelectiveMesh(const std::set& cellsToStructure, GapsFi for (auto it = boundaryCoordinatePairs.begin(); it != boundaryCoordinatePairs.end();) { const auto& [coord1, coord2] = *it; - bool alignedInXY = (coord1[2] == coord2[2]) && (coord1[0] == coord2[0] || coord1[1] == coord2[1]); - bool alignedInXZ = (coord1[1] == coord2[1]) && (coord1[0] == coord2[0] || coord1[2] == coord2[2]); - bool alignedInYZ = (coord1[0] == coord2[0]) && (coord1[1] == coord2[1] || coord1[2] == coord2[2]); + bool alignedInXY = (coord1[Z] == coord2[Z]) && (coord1[X] == coord2[X] || coord1[Y] == coord2[Y]); + bool alignedInXZ = (coord1[Y] == coord2[Y]) && (coord1[X] == coord2[X] || coord1[Z] == coord2[Z]); + bool alignedInYZ = (coord1[X] == coord2[X]) && (coord1[Y] == coord2[Y] || coord1[Z] == coord2[Z]); if (alignedInXY || alignedInXZ || alignedInYZ) { @@ -224,134 +225,127 @@ Mesh Staircaser::getSelectiveMesh(const std::set& cellsToStructure, GapsFi } } - fillGaps(boundaryCoordinatePairs); + fillGaps(boundaryCoordinatePairs); return mesh_; } void Staircaser::fillGaps(const RelativePairSet boundaryCoordinatePairs) { - std::set> uniqueElementsByVertices; - for (const auto& element : mesh_.groups[0].elements) { - uniqueElementsByVertices.insert(element.vertices); - } - CoordinateMap coordinateMap = buildCoordinateMap(mesh_.coordinates); - for (const auto& [coord1, coord2] : boundaryCoordinatePairs) { - auto v1 = coordinateMap.at(coord1); - auto v2 = coordinateMap.at(coord2); - std::pair edge = std::make_pair(v1, v2); - - auto commonNeighbors = findCommonNeighborsVertices(mesh_, edge); - - auto triangles = findTrianglesWithEdge(mesh_, edge); - bool correctOrientation; - ElementId thirdVertex; - - if (!triangles.empty()) { - for (const auto& elem : triangles) { - thirdVertex = -1; - const auto& verts = elem.vertices; - auto itV1 = std::find(verts.begin(), verts.end(), v1); - auto itV2 = std::find(verts.begin(), verts.end(), v2); - - auto idx1 = static_cast(std::distance(verts.begin(), itV1)); - auto idx2 = static_cast(std::distance(verts.begin(), itV2)); - correctOrientation = (idx2 == (idx1 + 1) % 3); - } + std::vector toRemove(mesh_.groups.size()); + + for (GroupId g = 0; g < mesh_.groups.size(); ++g) { + std::set> uniqueElementsByVertices; + auto& group = mesh_.groups[g]; + for (const auto& element : group.elements) { + uniqueElementsByVertices.insert(element.vertices); } - if (fillerType_ == GapsFillingType::Insert) { - for (const auto& neighborVertex : commonNeighbors) { - Element triangle; - triangle.type = Element::Type::Surface; - triangle.vertices = { v1, v2, neighborVertex }; + for (const auto& [coord1, coord2] : boundaryCoordinatePairs) { + auto firstVertexId = coordinateMap.at(coord1); + auto secondVertexId = coordinateMap.at(coord2); + std::pair edge = std::make_pair(firstVertexId, secondVertexId); - if (!correctOrientation) { - std::swap(triangle.vertices[1], triangle.vertices[2]); - } + auto commonNeighbors = findCommonNeighborsVertices(mesh_, edge); - auto minIt = std::min_element(triangle.vertices.begin(), triangle.vertices.end()); - std::rotate(triangle.vertices.begin(), minIt, triangle.vertices.end()); + auto triangleIds = findTrianglesWithEdge(group.elements, edge); + bool correctOrientation = false; + ElementId thirdVertexId = -1; - bool elementAlreadyExists = false; + if (!triangleIds.empty()) { + for (auto& e : triangleIds) { + const auto& triangle = group.elements[e]; + const auto& verts = triangle.vertices; + auto itVertex1 = std::find(verts.begin(), verts.end(), firstVertexId); + auto itVertex2 = std::find(verts.begin(), verts.end(), secondVertexId); - for (const auto& existing : uniqueElementsByVertices) { - auto existingVerts = existing; - auto minItExisting = std::min_element(existingVerts.begin(), existingVerts.end()); - std::rotate(existingVerts.begin(), minItExisting, existingVerts.end()); + auto v1 = static_cast(std::distance(verts.begin(), itVertex1)); + auto v2 = static_cast(std::distance(verts.begin(), itVertex2)); - if (existingVerts == triangle.vertices) { - elementAlreadyExists = true; - break; + if (v2 == (v1 + 1) % 3) { + correctOrientation = true; + thirdVertexId = verts[(v2 + 1) % 3]; + } + else { + correctOrientation = false; + thirdVertexId = verts[(v1 + 1) % 3]; } - } - - if (!elementAlreadyExists) { - std::swap(triangle.vertices[1], triangle.vertices[2]); - mesh_.groups[0].elements.push_back(triangle); - uniqueElementsByVertices.insert(triangle.vertices); } } - } else if (fillerType_ == GapsFillingType::Split) { - for (const auto& neighborVertex : commonNeighbors) { - if (neighborVertex != thirdVertex) { - Element triangleToRemove; - triangleToRemove.type = Element::Type::Surface; - triangleToRemove.vertices = { v1, v2, thirdVertex }; - - Element triangle1; - triangle1.type = Element::Type::Surface; - triangle1.vertices = { v1, neighborVertex, thirdVertex }; + else { + continue; + } - Element triangle2; - triangle2.type = Element::Type::Surface; - triangle2.vertices = { neighborVertex, v2, thirdVertex }; + if (fillerType_ == GapsFillingType::Insert) { + for (const auto& neighborVertex : commonNeighbors) { + Element triangle; + triangle.type = Element::Type::Surface; + triangle.vertices = { firstVertexId, secondVertexId, neighborVertex }; if (!correctOrientation) { - std::swap(triangleToRemove.vertices[1], triangleToRemove.vertices[2]); - std::swap(triangle1.vertices[1], triangle1.vertices[2]); - std::swap(triangle2.vertices[1], triangle2.vertices[2]); + std::swap(triangle.vertices[1], triangle.vertices[2]); } - auto minIt = std::min_element(triangleToRemove.vertices.begin(), triangleToRemove.vertices.end()); - std::rotate(triangleToRemove.vertices.begin(), minIt, triangleToRemove.vertices.end()); + auto minIt = std::min_element(triangle.vertices.begin(), triangle.vertices.end()); + std::rotate(triangle.vertices.begin(), minIt, triangle.vertices.end()); - auto minIt1 = std::min_element(triangle1.vertices.begin(), triangle1.vertices.end()); - std::rotate(triangle1.vertices.begin(), minIt1, triangle1.vertices.end()); + bool elementAlreadyExists = false; - auto minIt2 = std::min_element(triangle2.vertices.begin(), triangle2.vertices.end()); - std::rotate(triangle2.vertices.begin(), minIt2, triangle2.vertices.end()); + for (const auto& existing : uniqueElementsByVertices) { + auto existingVerts = existing; + auto minItExisting = std::min_element(existingVerts.begin(), existingVerts.end()); + std::rotate(existingVerts.begin(), minItExisting, existingVerts.end()); - mesh_.groups[0].elements.push_back(triangle1); - mesh_.groups[0].elements.push_back(triangle2); + if (existingVerts == triangle.vertices) { + elementAlreadyExists = true; + break; + } + } - std::vector toRemove(mesh_.groups.size()); - for (GroupId g = 0; g < mesh_.groups.size(); ++g) { - const auto& elements = mesh_.groups[g].elements; - for (std::size_t i = 0; i < elements.size(); ++i) { - if (elements[i].type != Element::Type::Surface) { - continue; - } + if (!elementAlreadyExists) { + std::swap(triangle.vertices[1], triangle.vertices[2]); + mesh_.groups[0].elements.push_back(triangle); + uniqueElementsByVertices.insert(triangle.vertices); + } + } + } + else if (fillerType_ == GapsFillingType::Split) { + for (const auto& neighborVertex : commonNeighbors) { + if (neighborVertex != thirdVertexId) { + toRemove[g].insert(triangleIds.back()); + + Element triangle1; + triangle1.type = Element::Type::Surface; + triangle1.vertices = { firstVertexId, neighborVertex, thirdVertexId }; + + Element triangle2; + triangle2.type = Element::Type::Surface; + triangle2.vertices = { neighborVertex, secondVertexId, thirdVertexId }; + + if (!correctOrientation) { + std::swap(triangle1.vertices[1], triangle1.vertices[2]); + std::swap(triangle2.vertices[1], triangle2.vertices[2]); + } - auto verts = elements[i].vertices; - auto minIt = std::min_element(verts.begin(), verts.end()); - std::rotate(verts.begin(), minIt, verts.end()); + auto minIt1 = std::min_element(triangle1.vertices.begin(), triangle1.vertices.end()); + std::rotate(triangle1.vertices.begin(), minIt1, triangle1.vertices.end()); - if (verts == triangleToRemove.vertices) { - toRemove[g].insert(i); - } - } - } + auto minIt2 = std::min_element(triangle2.vertices.begin(), triangle2.vertices.end()); + std::rotate(triangle2.vertices.begin(), minIt2, triangle2.vertices.end()); - redundancyCleaner::removeElements(mesh_, toRemove); + mesh_.groups[0].elements.push_back(triangle1); + mesh_.groups[0].elements.push_back(triangle2); + } } } - } else { - break; + else { + break; + } } } + redundancyCleaner::removeElements(mesh_, toRemove); redundancyCleaner::removeDegenerateElements(mesh_); } @@ -363,10 +357,10 @@ std::pair Staircaser::obtainNewIndexForElement(const Element Relatives boundaryCoordinates; for (const auto& vertexIndex : e.vertices) { - const auto& vertexCoord = inputMesh_.coordinates[vertexIndex]; + const auto& vertex = inputMesh_.coordinates[vertexIndex]; bool isOnCellBoundary = false; - auto touchingCells = GridTools::getTouchingCells(vertexCoord); + auto touchingCells = GridTools::getTouchingCells(vertex); for (const auto& touchingCell : touchingCells) { if (cellsToStructure.count(touchingCell)) { @@ -377,24 +371,24 @@ std::pair Staircaser::obtainNewIndexForElement(const Element CoordinateId newIndex; if (isOnCellBoundary) { - auto relCoord = toRelative(calculateStaircasedCell(vertexCoord)); - auto it = coordinateMap.find(relCoord); + auto relativeCoordinate = toRelative(calculateStaircasedCell(vertex)); + auto it = coordinateMap.find(relativeCoordinate); if (it == coordinateMap.end()) { - mesh_.coordinates.push_back(relCoord); + mesh_.coordinates.push_back(relativeCoordinate); newIndex = int(mesh_.coordinates.size() - 1); - coordinateMap.emplace(relCoord, newIndex); + coordinateMap.emplace(relativeCoordinate, newIndex); } else { newIndex = it->second; } - boundaryCoordinates.push_back(relCoord); + boundaryCoordinates.push_back(relativeCoordinate); } else { - auto it = coordinateMap.find(vertexCoord); + auto it = coordinateMap.find(vertex); if (it == coordinateMap.end()) { - mesh_.coordinates.push_back(vertexCoord); + mesh_.coordinates.push_back(vertex); newIndex = int(mesh_.coordinates.size() - 1); - coordinateMap.emplace(vertexCoord, newIndex); + coordinateMap.emplace(vertex, newIndex); } else { newIndex = it->second; } @@ -407,46 +401,48 @@ std::pair Staircaser::obtainNewIndexForElement(const Element } bool Staircaser::isAllCoordinatesOnTheSameCellBoundary(const Element& newElement, const std::set& cellsToStructure) { - std::set commonStructuredCells; + std::set commonStaircasedCells; bool firstVertex = true; - for (const auto& vertex : newElement.vertices) { - const auto& vertexCoord = mesh_.coordinates[vertex]; - auto touchingCells = GridTools::getTouchingCells(vertexCoord); + for (const auto& vertexId : newElement.vertices) { + const auto& vertex = mesh_.coordinates[vertexId]; + auto touchingCells = GridTools::getTouchingCells(vertex); - std::set structuredTouchingCells; - for (const auto& c : touchingCells) { - if (cellsToStructure.count(c)) { - structuredTouchingCells.insert(c); + std::set staircasedTouchingCells; + for (const auto& cell : touchingCells) { + if (cellsToStructure.count(cell)) { + staircasedTouchingCells.insert(cell); } } if (firstVertex) { - commonStructuredCells = std::move(structuredTouchingCells); + commonStaircasedCells = std::move(staircasedTouchingCells); firstVertex = false; } else { std::set intersection; - std::set_intersection(commonStructuredCells.begin(), commonStructuredCells.end(), - structuredTouchingCells.begin(), structuredTouchingCells.end(), + std::set_intersection(commonStaircasedCells.begin(), commonStaircasedCells.end(), + staircasedTouchingCells.begin(), staircasedTouchingCells.end(), std::inserter(intersection, intersection.begin())); - commonStructuredCells = std::move(intersection); + commonStaircasedCells = std::move(intersection); } - if (commonStructuredCells.empty()) break; + if (commonStaircasedCells.empty()) { + break; + } } - return !commonStructuredCells.empty(); + return !commonStaircasedCells.empty(); } -std::set Staircaser::getElementsConvertedInLines(Cell cell, std::map> cellElemMap) { +std::set Staircaser::getElementsConvertedToLines(Cell cell, std::map> cellElemMap) { std::set elementsConvertedInLines; - for (const auto& e : cellElemMap.at(cell)) { - if (!e->vertices.empty()) { + for (const auto& element : cellElemMap.at(cell)) { + if (!element->vertices.empty()) { bool allVerticesAreEqual = std::all_of( - e->vertices.begin() + 1, - e->vertices.end(), - [&](int v) { return v == e->vertices.front(); } + element->vertices.begin() + 1, + element->vertices.end(), + [&](int v) { return v == element->vertices.front(); } ); if (allVerticesAreEqual) { @@ -454,27 +450,27 @@ std::set Staircaser::getElementsConvertedInLines(Cell cell, std::mapisTriangle()) { - const auto& firstVertexCoords = mesh_.coordinates[e->vertices[0]]; - const auto& secondVertexCoords = mesh_.coordinates[e->vertices[1]]; - const auto& thirdVertexCoords = mesh_.coordinates[e->vertices[2]]; + if (element->isTriangle()) { + const auto& firstVertex = mesh_.coordinates[element->vertices[0]]; + const auto& secondVertex = mesh_.coordinates[element->vertices[1]]; + const auto& thirdVertex = mesh_.coordinates[element->vertices[2]]; int equalCoords = 0; - for (int dim = 0; dim < 3; ++dim) { - if (firstVertexCoords[dim] == secondVertexCoords[dim] && - secondVertexCoords[dim] == thirdVertexCoords[dim]) { + for (Axis axis = X; axis <= Z; ++axis) { + if (firstVertex[axis] == secondVertex[axis] && + secondVertex[axis] == thirdVertex[axis]) { ++equalCoords; } } bool areTwoVerticesEqual = - (firstVertexCoords == secondVertexCoords) || - (secondVertexCoords == thirdVertexCoords) || - (firstVertexCoords == thirdVertexCoords); + (firstVertex == secondVertex) || + (secondVertex == thirdVertex) || + (firstVertex == thirdVertex); if (equalCoords == 2 && !areTwoVerticesEqual) { - elementsConvertedInLines.insert(*e); + elementsConvertedInLines.insert(*element); } } } @@ -482,11 +478,11 @@ std::set Staircaser::getElementsConvertedInLines(Cell cell, std::map elementsConvertedInLines, Group& meshGroup, const Cell cell, std::map> cellElemMap, std::vector> toRemove) { - for (const auto& e: elementsConvertedInLines) { +void Staircaser::splitLinesWithNeighborTriangle(const GroupId groupIndex, std::set elementsConvertedToLines, Group& meshGroup, const Cell cell, std::map> cellElemMap, std::vector> toRemove) { + for (const auto& element: elementsConvertedToLines) { bool processed = false; - for (const auto& otherElementsInCell: cellElemMap.at(cell)) { - if (e == *otherElementsInCell) { + for (const auto& otherElementInCell: cellElemMap.at(cell)) { + if (element == *otherElementInCell) { continue; } @@ -494,10 +490,10 @@ void Staircaser::splitLinesWithNeighborTriangle(const size_t groupIndex, std::se int sharedCount = 0; bool correctOrientation = false; - for (int i = 0; i < otherElementsInCell->vertices.size(); ++i) { - int vOther = otherElementsInCell->vertices[i]; + for (int v = 0; v < otherElementInCell->vertices.size(); ++v) { + int vOther = otherElementInCell->vertices[v]; - if (std::find(e.vertices.begin(), e.vertices.end(), vOther) != e.vertices.end()) { + if (std::find(element.vertices.begin(), element.vertices.end(), vOther) != element.vertices.end()) { sharedVertices.insert(vOther); ++sharedCount; } @@ -506,52 +502,52 @@ void Staircaser::splitLinesWithNeighborTriangle(const size_t groupIndex, std::se bool shareExactlyTwo = (sharedCount == 2); - if(shareExactlyTwo && otherElementsInCell->isTriangle()) { + if(shareExactlyTwo && otherElementInCell->isTriangle()) { - auto v1 = *sharedVertices.begin(); - auto v2 = *std::next(sharedVertices.begin()); + auto vertexId1 = *sharedVertices.begin(); + auto vertexId2 = *std::next(sharedVertices.begin()); - auto itV1 = std::find(e.vertices.begin(), e.vertices.end(), v1); - auto itV2 = std::find(e.vertices.begin(), e.vertices.end(), v2); - auto idx1 = static_cast(std::distance(e.vertices.begin(), itV1)); - auto idx2 = static_cast(std::distance(e.vertices.begin(), itV2)); + auto itV1 = std::find(element.vertices.begin(), element.vertices.end(), vertexId1); + auto itV2 = std::find(element.vertices.begin(), element.vertices.end(), vertexId2); + auto idx1 = static_cast(std::distance(element.vertices.begin(), itV1)); + auto idx2 = static_cast(std::distance(element.vertices.begin(), itV2)); correctOrientation = (idx2 == (idx1 + 1) % 3); if(!correctOrientation) { - std::swap(v1, v2); + std::swap(vertexId1, vertexId2); } - CoordinateId v4 = -1; - for (const auto& v : e.vertices) { - if (v != v1 && v != v2) { - v4 = v; + CoordinateId vertexId4 = -1; + for (const auto& vertexId : element.vertices) { + if (vertexId != vertexId1 && vertexId != vertexId2) { + vertexId4 = vertexId; break; } } - CoordinateId v3 = -1; - for (const auto& v : otherElementsInCell->vertices) { - if (v != v1 && v != v2) { - v3 = v; + CoordinateId vertexId3 = -1; + for (const auto& vertexId : otherElementInCell->vertices) { + if (vertexId != vertexId1 && vertexId != vertexId2) { + vertexId3 = vertexId; break; } } Element triangle1; triangle1.type = Element::Type::Surface; - triangle1.vertices = { v3, v2, v4 }; + triangle1.vertices = { vertexId3, vertexId2, vertexId4 }; Element triangle2; triangle2.type = Element::Type::Surface; - triangle2.vertices = { v4, v1, v3 }; + triangle2.vertices = { vertexId4, vertexId1, vertexId3 }; - auto itOtherElement = std::find(meshGroup.elements.begin(), meshGroup.elements.end(), *otherElementsInCell); + auto itOtherElement = std::find(meshGroup.elements.begin(), meshGroup.elements.end(), *otherElementInCell); if (itOtherElement != meshGroup.elements.end()) { ElementId otherElementId = std::distance(meshGroup.elements.begin(), itOtherElement); toRemove[groupIndex].insert(otherElementId); } - auto itElement = std::find(meshGroup.elements.begin(), meshGroup.elements.end(), e); + auto itElement = std::find(meshGroup.elements.begin(), meshGroup.elements.end(), element); if (itElement != meshGroup.elements.end()) { ElementId elementId = std::distance(meshGroup.elements.begin(), itElement); toRemove[groupIndex].insert(elementId); @@ -567,7 +563,9 @@ void Staircaser::splitLinesWithNeighborTriangle(const size_t groupIndex, std::se break; } } - if(processed) {continue;} + if(processed) { + continue; + } } } diff --git a/src/core/Staircaser.h b/src/core/Staircaser.h index a0a94e4..04eb0b7 100644 --- a/src/core/Staircaser.h +++ b/src/core/Staircaser.h @@ -78,8 +78,8 @@ class Staircaser : public utils::GridTools { void fillGaps(const RelativePairSet boundaryCoordinatePairs); std::pair obtainNewIndexForElement(const Element& e, const std::set& cellsToStructure, CoordinateMap& coordinateMap); bool isAllCoordinatesOnTheSameCellBoundary(const Element& newElement, const std::set& cellsToStructure); - std::set getElementsConvertedInLines(const Cell cell, std::map> cellElemMap); - void splitLinesWithNeighborTriangle(const size_t groupIndex, std::set elementsConvertedInLines, Group& meshGroup, const Cell cell, std::map> cellElemMap, std::vector> toRemove); + std::set getElementsConvertedToLines(const Cell cell, std::map> cellElemMap); + void splitLinesWithNeighborTriangle(const GroupId groupIndex, std::set elementsConvertedToLines, Group& meshGroup, const Cell cell, std::map> cellElemMap, std::vector> toRemove); }; diff --git a/test/core/StaircaserTest.cpp b/test/core/StaircaserTest.cpp index 4975e5f..6d1aa5e 100644 --- a/test/core/StaircaserTest.cpp +++ b/test/core/StaircaserTest.cpp @@ -19,6 +19,43 @@ namespace meshlib::core { class StaircaserTest : public ::testing::Test { protected: + static void assertCoordinatesListEquals(const Coordinates& expectedCoordinates, const Coordinates& resultCoordinates) { + ASSERT_EQ(expectedCoordinates.size(), resultCoordinates.size()); + + for (CoordinateId c = 0; c < expectedCoordinates.size(); ++c) { + auto& expectedCoordinate = expectedCoordinates[c]; + auto& resultCoordinate = resultCoordinates[c]; + + for (Axis axis = X; axis <= Z; ++axis) { + EXPECT_EQ(expectedCoordinate[axis], resultCoordinate[axis]) + << "Current coordinate: #" << c << std::endl + << "Current Axis: #" << axis << std::endl; + } + } + } + + static void assertElementsListEquals(const Elements& expectedElements, const Elements& resultElements, GroupId g = 0) { + ASSERT_EQ(expectedElements.size(), resultElements.size()); + + for (ElementId e = 0; e < expectedElements.size(); ++e) { + const Element& expectedElement = expectedElements[e]; + const Element& resultElement = resultElements[e]; + + EXPECT_EQ(resultElement.vertices.size(), expectedElement.vertices.size()) + << "Current Group: #" << g << std::endl + << "Current Element: #" << e << std::endl; + EXPECT_EQ(resultElement.type, expectedElement.type) + << "Current Group: #" << g << std::endl + << "Current Element: #" << e << std::endl; + + for (std::size_t v = 0; v < resultElement.vertices.size(); ++v) { + EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]) + << "Current Group: #" << g << std::endl + << "Current Element: #" << e << std::endl + << "Current Vertex: #" << v << std::endl; + } + } + } }; TEST_F(StaircaserTest, calculateStaircasedRelativeInExactSteps) @@ -92,7 +129,8 @@ TEST_F(StaircaserTest, calculateStaircasedRelativeBetweenSteps) auto resultCell = staircaser.calculateStaircasedCell(newRelative); for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultCell[axis], expectedLowerCell[axis]); + EXPECT_EQ(resultCell[axis], expectedLowerCell[axis]) + << "Relative: (" << newRelative[X] << ", " << newRelative[Y] << ", " << newRelative[Z] << ")" << std::endl; } for (std::size_t axis = 0; axis < 3; ++axis) { @@ -102,7 +140,8 @@ TEST_F(StaircaserTest, calculateStaircasedRelativeBetweenSteps) resultCell = staircaser.calculateStaircasedCell(newRelative); for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultCell[axis], expectedUpperCell[axis]); + EXPECT_EQ(resultCell[axis], expectedUpperCell[axis]) + << "Relative: (" << newRelative[X] << ", " << newRelative[Y] << ", " << newRelative[Z] << ")" << std::endl; } for (std::size_t axis = 0; axis < 3; ++axis) { @@ -112,7 +151,8 @@ TEST_F(StaircaserTest, calculateStaircasedRelativeBetweenSteps) resultCell = staircaser.calculateStaircasedCell(newRelative); for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultCell[axis], expectedUpperCell[axis]); + EXPECT_EQ(resultCell[axis], expectedUpperCell[axis]) + << "Relative: (" << newRelative[X] << ", " << newRelative[Y] << ", " << newRelative[Z] << ")" << std::endl; } } } @@ -192,30 +232,13 @@ TEST_F(StaircaserTest, transformNodesBySnappingThemIntoCellIntersections) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - - for (std::size_t index = 0; index < expectedRelatives.size(); ++index) { - for (std::size_t axis = X; axis <= Z; ++axis) { - EXPECT_EQ(resultMesh.coordinates[index][axis], expectedRelatives[index][axis]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < resultMesh.groups.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - ASSERT_EQ(resultGroup.elements.size(), expectedGroup.size()); - - for (std::size_t e = 0; e < resultGroup.elements.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - EXPECT_TRUE(resultElement.isNode()); - - EXPECT_EQ(resultElement.vertices[0], expectedElement.vertices[0]); - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -227,7 +250,7 @@ TEST_F(StaircaserTest, transformSingleSegmentsIntoSingleStaircasedElements) // | | | ║ | // | | _-1 | -> ║ | // | 0-‾ | ║ | - // *---------* 0=========1 + // *---------* 0═════════1 float lowerCoordinateValue = -5.0; float upperCoordinateValue = 5.0; @@ -261,47 +284,36 @@ TEST_F(StaircaserTest, transformSingleSegmentsIntoSingleStaircasedElements) Relative({ 0.0, 0.0, 1.0 }), }; - Elements expectedElements = { - Element({0, 1}, Element::Type::Line), - Element({0, 2}, Element::Type::Line), - Element({0, 3}, Element::Type::Line) + std::vector expectedElements = { + { + Element({0, 1}, Element::Type::Line) + }, + { + Element({0, 2}, Element::Type::Line), + }, + { + Element({0, 3}, Element::Type::Line) + }, }; auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t g = 0; g < resultMesh.groups.size(); ++g) { - ASSERT_EQ(resultMesh.groups[g].elements.size(), 1); - } - - for (std::size_t g = 0; g < expectedRelatives.size(); ++g) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[g][axis], expectedRelatives[g][axis]); - } - } - + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedElement = expectedElements[g]; - - EXPECT_TRUE(resultGroup.elements[0].isLine()); - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultGroup.elements[0].vertices[v], expectedElement.vertices[v]); - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } TEST_F(StaircaserTest, transformSingleSegmentsIntoTwoStaircasedElements) { - // *-----------* {0.5->1}======{1->2} *-----------* *----------{3->2} + // *-----------* {0.5->1}══════{1->2} *-----------* *----------{3->2} // | 1 | ║ | | 3 | | ║ // | / | ║ | | / | | ║ // | / | -> ║ | | / | -> | ║ // | 0 | ║ | | 2 | | ║ - // *-----------* 0-----------* *-----------* {2->0}======={2.5->3} + // *-----------* 0-----------* *-----------* {2->0}═══════{2.5->3} float lowerCoordinateValue = -5.0; float upperCoordinateValue = 5.0; @@ -382,34 +394,12 @@ TEST_F(StaircaserTest, transformSingleSegmentsIntoTwoStaircasedElements) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t g = 0; g < resultMesh.groups.size(); ++g) { - ASSERT_EQ(resultMesh.groups[g].elements.size(), 2); - } - - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - EXPECT_TRUE(resultGroup.elements[0].isLine()); - EXPECT_TRUE(resultGroup.elements[1].isLine()); - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -419,23 +409,23 @@ TEST_F(StaircaserTest, transformSingleSegmentsWithinDiagonalIntoTwoStaircasedEle { // y y y y - // *-------* *--------{1->2} *-------* {0.5->1}====={1->2} + // *-------* *--------{1->2} *-------* {0.5->1}═════{1->2} // | 1| | ║ | 1| ║ | // | ╱ | | ║ | ╱ | ║ | // | ╱ | -> | ║ | ╱ | -> ║ | // |0 | | ║ |0 | ║ | - // *-------* x 0======{0.5->1} x *-------* z 0-----------* z + // *-------* x 0══════{0.5->1} x *-------* z 0-----------* z // y y y y - // *-------* *----------0 *-------* {0.5->1}========0 + // *-------* *----------0 *-------* {0.5->1}════════0 // | 0| | ║ | 3| ║ | // | ╱ | | ║ | ╱ | ║ | // | ╱ | -> | ║ | ╱ | -> ║ | // |1 | | ║ |4 | ║ | - // *-------* x {1->2}==={0.5->1} x *-------* z {1->2}--------* z + // *-------* x {1->2}═══{0.5->1} x *-------* z {1->2}--------* z // y y y y - // *-------* {1->2}===={0.5->1} *-------* {1->2}======{0.5->1} + // *-------* {1->2}════{0.5->1} *-------* {1->2}══════{0.5->1} // |1 | | ║ |1 ⎸ | ║ // | \ | | ║ | \ ⎸ | ║ // | \ | -> | ║ | \ ⎸ -> | ║ @@ -443,7 +433,7 @@ TEST_F(StaircaserTest, transformSingleSegmentsWithinDiagonalIntoTwoStaircasedEle // *-------* x *----------0 x *-------* z *------------0 z // y y y y - // *-------* 0======{0.5->1} *-------* 0======={0.5->1} + // *-------* 0══════{0.5->1} *-------* 0═══════{0.5->1} // |0 | | ║ |0 | ⎹ ║ // | \ | | ║ | \ | ⎹ ║ // | \ | -> | ║ | \ | -> ⎹ ║ @@ -577,31 +567,12 @@ TEST_F(StaircaserTest, transformSingleSegmentsWithinDiagonalIntoTwoStaircasedEle auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t g = 0; g < resultMesh.groups.size(); ++g) { - ASSERT_EQ(resultMesh.groups[g].elements.size(), 2); - } - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - EXPECT_TRUE(resultElement.isLine()); - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -615,7 +586,7 @@ TEST_F(StaircaserTest, transformSingleSegmentsIntoThreeStaircasedElements) // z/ | 1 / | z/ | / ║ // *---┼-----==‾+* | -> *---┼---------* ║ // | |y _-‾ ¦| | | |y | ║ - // | _*==------┴┼---* |{0.33->1}===={0.67->2} + // | _*==------┴┼---* |{0.33->1}════{0.67->2} // |0‾/ | / | ⫽ ⎸ / // |¦/ | / | ⫽ ⎸ / // |/ |/ |⫽ ⎸/ @@ -687,35 +658,12 @@ TEST_F(StaircaserTest, transformSingleSegmentsIntoThreeStaircasedElements) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t g = 0; g < resultMesh.groups.size(); ++g) { - ASSERT_EQ(resultMesh.groups[g].elements.size(), 3); - } - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - EXPECT_TRUE(resultGroup.elements[0].isLine()); - EXPECT_TRUE(resultGroup.elements[1].isLine()); - EXPECT_TRUE(resultGroup.elements[2].isLine()); - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) - { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -723,17 +671,17 @@ TEST_F(StaircaserTest, transformSingleSegmentsIntoThreeStaircasedElements) TEST_F(StaircaserTest, transformSingleSegmentsWithinDiagonalIntoThreeStaircasedElements) { - // *-------------* *----------{1->3} - // /| 1/| /| /║ - // / | ╱¦ | / | / ║ - // z/ | ╱ /¦ | z/ | / ║ - // *---┼------╱--* ¦ | -> *---┼---------* ║ - // | |y ╱ | ¦ | | |y | ║ + // *-------------* *----------{1->3} + // /| 1/| /| /║ + // / | ╱¦ | / | / ║ + // z/ | ╱ /¦ | z/ | / ║ + // *---┼------╱--* ¦ | -> *---┼---------* ║ + // | |y ╱ | ¦ | | |y | ║ // | *--╱------┼-┼-* | *---------{0.67->2} - // | / ╱ | / | / | ⫽ - // | /╱ | / | / | ⫽ - // |⌿0 |/ |/ |⫽ - // *-------------* x 0========={0.33->1} x + // | / ╱ | / | / | ⫽ + // | /╱ | / | / | ⫽ + // |⌿0 ⎹/ ⎹/ ⎹⫽ + // *-------------* x 0═════════{0.33->1} x float lowerRelativeValue = -5.0; float upperRelativeValue = 5.0; @@ -835,34 +783,12 @@ TEST_F(StaircaserTest, transformSingleSegmentsWithinDiagonalIntoThreeStaircasedE auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t g = 0; g < resultMesh.groups.size(); ++g) { - ASSERT_EQ(resultMesh.groups[g].elements.size(), 3); - } - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - EXPECT_TRUE(resultGroup.elements[0].isLine()); - EXPECT_TRUE(resultGroup.elements[1].isLine()); - EXPECT_TRUE(resultGroup.elements[2].isLine()); - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -870,7 +796,7 @@ TEST_F(StaircaserTest, transformSingleSegmentsWithinDiagonalIntoThreeStaircasedE TEST_F(StaircaserTest, transformSingleSegmentsParallelWithDiagonalIntoThreeStaircasedElements) { - // *----------1--* {0.67->2}======={1->3} + // *----------1--* {0.67->2}═══════{1->3} // /| ╱¦ /| /║ /| // / | ╱ ¦╱ | / ║ / | // z/ | ╱ ╱ | z/ ║ / | @@ -879,7 +805,7 @@ TEST_F(StaircaserTest, transformSingleSegmentsParallelWithDiagonalIntoThreeStair // | ╱---------┼┼--* {0.67->2}-----┼---* // | ╱/ | / | ⫽ ⎸ / // |0/ | / | ⫽ ⎸ / - // |∤ ⎹/ ⎹⫽ |/ + // |∤ ⎸/ ⎸⫽ ⎹ / // *-------------* x 0-------------* x float lowerCoordinateValue = -5.0; @@ -970,34 +896,12 @@ TEST_F(StaircaserTest, transformSingleSegmentsParallelWithDiagonalIntoThreeStair auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t g = 0; g < resultMesh.groups.size(); ++g) { - ASSERT_EQ(resultMesh.groups[g].elements.size(), 3); - } - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - EXPECT_TRUE(resultGroup.elements[0].isLine()); - EXPECT_TRUE(resultGroup.elements[1].isLine()); - EXPECT_TRUE(resultGroup.elements[2].isLine()); - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -1012,8 +916,8 @@ TEST_F(StaircaserTest, transformSingleSegmentsIntoNodes) // | 3 | | -> | | | // | 1 | | | | | // | / | _6 | | | | - // | 0 4-------5-‾ | | | | - // *-------------*-------------* {0|1->0}======={5->3}----------* + // | 0 4───────5-‾ | | | | + // *-------------*-------------* {0|1->0}═══════{5->3}----------* // {4->2} {5|6->3} float lowerCoordinateValue = -5.0; @@ -1068,34 +972,12 @@ TEST_F(StaircaserTest, transformSingleSegmentsIntoNodes) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - ASSERT_EQ(resultMesh.groups[0].elements.size(), 1); - ASSERT_EQ(resultMesh.groups[1].elements.size(), 1); - ASSERT_EQ(resultMesh.groups[2].elements.size(), 2); - - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - EXPECT_TRUE(resultMesh.groups[0].elements[0].isNode()); - EXPECT_TRUE(resultMesh.groups[1].elements[0].isNode()); - ASSERT_TRUE(resultMesh.groups[2].elements[0].isLine()); - EXPECT_TRUE(resultMesh.groups[2].elements[1].isNode()); + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - for (std::size_t e = 0; e < resultGroup.elements.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedElements[g][e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -1110,8 +992,8 @@ TEST_F(StaircaserTest, transformGroupsWithMultipleLines) // | | _2 | -> | | ║ // | | _-‾ | | | ║ // | | _-‾ | | | ║ - // | 0-------1-‾ | | | ║ - // *-------------*-------------* 0=============1=========={1.5->2} + // | 0───────1-‾ | | | ║ + // *-------------*-------------* 0═════════════1══════════{1.5->2} // float lowerCoordinateValue = -5.0; @@ -1149,28 +1031,10 @@ TEST_F(StaircaserTest, transformGroupsWithMultipleLines) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), 1); - ASSERT_EQ(resultMesh.groups[0].elements.size(), expectedElements.size()); - - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - ASSERT_TRUE(resultMesh.groups[0].elements[0].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[1].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[2].isLine()); - - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } @@ -1179,12 +1043,12 @@ TEST_F(StaircaserTest, transformTriangleIntoStaircasedSurface) { // y y y y - // *----------* {2->3}====={1->2} *----------* 1==========2 + // *----------* {2->3}═════{1->2} *----------* 1══════════2 // | __1 | ║\\\\\\\\\\║ | __->2 | ║//////////║ // | 2<-‾‾ ╱ | ║\\\\\\\\\\║ | 1-- / | ║//////////║ // | \ ╱ | -> ║\\\\\\\\\\║ | \ ╱ | -> ║//////////║ // | 0 | ║\\\\\\\\\\║ | 0 | ║//////////║ - // *----------* x 0======{0.5->1} x *----------* x 0======(2.5->3) x + // *----------* x 0══════{0.5->1} x *----------* x 0══════(2.5->3) x float lowerCoordinateValue = -5.0; float upperCoordinateValue = 5.0; @@ -1221,33 +1085,12 @@ TEST_F(StaircaserTest, transformTriangleIntoStaircasedSurface) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t g = 0; g < resultMesh.groups.size(); ++g) { - ASSERT_EQ(resultMesh.groups[g].elements.size(), 2); - } - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - EXPECT_TRUE(resultGroup.elements[0].isQuad()); - EXPECT_TRUE(resultGroup.elements[1].isQuad()); - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -1261,7 +1104,7 @@ TEST_F(StaircaserTest, transformTriangleIntoNode) // / | ╱ | / | / | // z/ | ╱ | z/ | / | // *---2---------* | -> *---┼---------* | - // | ||y⟍ ⎸ ⎸ | | y | | + // | ||y⟍ ⎸ ⎸ ⎸ ⎸ y ⎸ ⎸ // | ⎹ *-_=1-----┼---* | {0|1|2}-----┼---* // | 0/-‾ | / | / | / // | / | / | / | / @@ -1300,32 +1143,12 @@ TEST_F(StaircaserTest, transformTriangleIntoNode) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t g = 0; g < resultMesh.groups.size(); ++g) { - ASSERT_EQ(resultMesh.groups[g].elements.size(), 3); - } - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - EXPECT_TRUE(resultGroup.elements[0].isNode()); - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -1334,16 +1157,16 @@ TEST_F(StaircaserTest, transformTriangleIntoNode) TEST_F(StaircaserTest, transformTriangleIntoLines) { // *-------------* 2-------------* *-------------* {4->2}----------* - // /| /| /⦀ /| /| /| /⦀ /⎹ - // /┄|┄┄┄2 ╱ | / ⫼ / | / | 4 ╱ | / ⦀ / ⎹ - // z/ | ⎹| ╱ | z/ ⦀ / | z/ | ⎸⎸ ╱ | z/ ⦀ / ⎹ - // *---┼--┼┼-----* | -> *---⫵---------* ⎸ *---┼-┼-┼----* ⎸ -> *---⫵--------* | - // | |y⎹ | | | ⎹ ⦀ y | | | |y| ⎸ | | | ⫵ ⎹ ⎹ - // | ┄*┄⎸┄1-----┼---* ⎹ 1---------┼---* | *-⎸--⎹-⋰-┼----* |{3.5->1}≡≡≡≡≡╪{5->3} - // | / ⎹ ⟋ ⎸ / | ⫻ | / | / ⎹ _-5 ⎸ / ⎹ ⫻ | / - // | ┄┄┄0 | / ⎹ ⫻ ⎹ / ⎹ ┄┄┄3-‾ | / ⎹ ⫻ ⎹ / - // |/ |/ ⎹⫻ ⎹/ ⎹/ |/ ⎹⫻ ⎹/ - // *-------------* x 0-------------* x *------------* x {3->0}-----------* x + // /| /| /⦀ /| /⎹ /| /⦀ /⎹ + // /┄|┄┄┄2 ╱ | / ⫼ / | / ⎹ 4 ╱ | / ⦀ / ⎹ + // z/ | ⎹| ╱ | z/ ⦀ / | z/ ⎹ ⎸⎸ ╱ | z/ ⦀ / ⎹ + // *---┼--┼┼-----* | -> *---⫵---------* ⎸ *---┼-┼-┼-----* ⎸ -> *---⫵---------* ⎸ + // | |y⎹ | | | ⎹ ⦀ y | | | ⎹y| ⎸ ⎸ | | ⫵ ⎹ | + // | ┄*┄⎸┄1-----┼---* ⎹ 1---------┼---* | *-⎸--⎹-⋰-┼---* ⎹{3.5->1}≡≡≡≡≡≡╪{5->3} + // | / ⎹ ⟋ ⎸ / | ⫻ | / | / ⎹ _-5 ⎸ / | ⫻ | / + // | ┄┄┄0 | / ⎹ ⫻ ⎹ / ⎹ ┄┄┄3-‾ | / ⎹ ⫻ ⎹ / + // |/ |/ ⎹⫻ ⎹/ ⎹/ |/ ⎹⫻ ⎹/ + // *-------------* x 0-------------* x *-------------* x {3->0}-----------* x float lowerCoordinateValue = -5.0; float upperCoordinateValue = 5.0; @@ -1391,49 +1214,28 @@ TEST_F(StaircaserTest, transformTriangleIntoLines) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - EXPECT_EQ(resultMesh.groups[0].elements.size(), 4); - ASSERT_EQ(resultMesh.groups[1].elements.size(), 6); - - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - EXPECT_TRUE(expectedElement.isLine()); - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } TEST_F(StaircaserTest, transformTriangleIntoSurfacesAndLines) { - // *-------------* *-----------{2->3} *-------------* *-------------{1->3} - // /| /| /| ⫽║ /| / ⎸ /| / ⦀ - // / | ╱_2 / | ⫽/║ / | / ⎸ / | / ⦀ - // z/ | _/‾|| z/ | ⫽//║ z/ | ⌿-1 | z / | / ⦀ - // *---┼------=-‾* | | -> *---┼----{1.5->2}/║ *---┼------_-‾* ⎹┆ | -> *---┼-----{1.5->2} ⦀ - // | |y _-‾ | ⎸ | | | y ║///║ | |y _-‾ | |┆ | | | y | ⦀ - // | *_-=------┼┼--* | *---------╫{3.5->4} | *_-=------┼⎹-┴-* |{0.33->1}=====╪={0.66->2} - // | _-‾ __ --1 / | / ║//⫽ | 0⌿=-___ || / | ⫽///////////|///⫽ - // ├0-- ‾‾ | / | / ║/⫽ | ∤ ‾‾‾--2 / ⎹ ⫽////////////⎹//⫽ - // |/ |/ |/ ║⫽ |/ |/ |⫽/////////////|⫽ - // *-------------* x 0≡≡≡≡≡≡≡≡≡≡≡≡≡1 x *-------------* x 0============{2->4} x + // *-------------* *-----------{2->3} *--------------* *-------------{1->3} + // /| /| /| ⫽║ /⎸ / ⎸ /| / ⦀ + // / | ╱_2 / | ⫽/║ / ⎸ / ⎸ / | / ⦀ + // z/ | _/‾|| z/ | ⫽//║ z/ ⎸ ⌿-1 | z / | / ⦀ + // *---┼------=-‾* | | -> *---┼----{1.5->2}/⎹⎸ *---┼------_-‾* ⎹┆ | -> *---┼-----{1.5->2} ⦀ + // | |y _-‾ | ⎸ | | | y ║///⎹⎸ ⎹ |y _-‾ | |┆ | | | y | ⦀ + // | *_-=------┼┼--* | *---------╫{3.5->4} ⎹ *_-=------┼⎹-┴-* |{0.33->1}═════╪={0.66->2} + // | _-‾ __ --1 / | / ║//⫽ ⎸ 0⌿=-___ || / | ⫽//////////⎹///⫽ + // ├0-- ‾‾ | / | / ║/⫽ ⎸ ∤ ‾‾‾--2 / ⎹ ⫽///////////⎹//⫽ + // |/ |/ |/ ║⫽ ⎸/ |/ ⎸⫽////////////⎸⫽ + // *-------------* x 0≡≡≡≡≡≡≡≡≡≡≡≡≡1 x *-------------* x 0════════════{2->4} x float lowerCoordinateValue = -5.0; float upperCoordinateValue = 5.0; @@ -1479,56 +1281,28 @@ TEST_F(StaircaserTest, transformTriangleIntoSurfacesAndLines) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - EXPECT_EQ(resultMesh.groups[0].elements.size(), 3); - ASSERT_EQ(resultMesh.groups[1].elements.size(), 3); - - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - - EXPECT_TRUE(resultMesh.groups[0].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[0].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[0].elements[2].isLine()); - EXPECT_TRUE(resultMesh.groups[1].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[1].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[1].elements[2].isLine()); - + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } TEST_F(StaircaserTest, transformTriangleIntoTwoSurfaces) { - // *--------------* *-----------{2->3} *---------------* *---------------0 - // /| /| /⎹ ⫽║ /| /| /| ⫽║ - // / | /⟋2 / ⎹ ⫽/║ / | /⟋2 / | ⫽\║ - // z/ | ⟋/ ⎹| z/ | ⫽//║ z/ ⎸ ⟋/ ⎹| z/ | ⫽\\║ - // *---┼--------⟋-* ⎸| -> *---┼-----{1.5->2}/║ *---┼--------⟋-* ⎸| -> *--┼-----{0.5->1}\\║ - // | |y ⟋ | | | | | y ║///║ | |y ⟋ | | | ⎹ |y ║\\\║ - // | *--⟋-------┼┼--* |{2.66->5}=====║{2.33->4} | *--⟋-------┼┼--* ⎹{2.33->4}======║{2.66->5} - // | /⟋ __-1 / | ⫽\\\\\\\\\\\║//⫽ ⎸ /⟋ __-1 / | ⫽///////////║\\⫽ - // |⟋/ __--‾‾ | / | ⫽\\\\\\\\\\\\║/⫽ ⎸⟋/ __--‾‾ | / | ⫽////////////║\⫽ - // 0⌿-‾‾ ⎹/ ⎹⫽\\\\\\\\\\\\\║⫽ 0⌿-‾‾ |/ |⫽/////////////║⫽ - // *--------------* x 0==============1 x *--------------* x {2->3}========={1->2} x + // *--------------* *-------------{2->3} *---------------* *----------------0 + // /| /| /⎹ ⫽║ /| /⎸ /⎸ ⫽⎹⎸ + // / | /⟋2 / | ⫽/⎹⎸ / ⎸ /⟋2 / | ⫽\\║ + // z/ | ⟋/ ⎹| z/ | ⫽//⎹⎸ z/ ⎸ ⟋/ |⎸ z/ ⎸ ⫽\\\║ + // *---┼--------⟋-* ⎸| -> *---┼-----{1.5->2}//║ *---┼--------⟋-* ⎸| -> *---┼-----{0.5->1}\\\\║ + // | |y ⟋ | | | ⎸ | y ║////║ | |y ⟋ | | | ⎸ |y ║\\\\\║ + // | *--⟋-------┼┼--* ⎸{2.66->5}═════║{2.33->4} | *--⟋-------┼┼--* ⎸{2.33->4}══════║{2.66->5} + // | /⟋ __-1 / ⎸ ⫽\\\\\\\\\\⎹⎸//⫽ | /⟋ __--1 / ⎸ ⫽///////////⎹⎸\\\⫽ + // |⟋/ __--‾‾ ⎸ / ⎸ ⫽\\\\\\\\\\\⎹⎸/⫽ |⟋/ __--‾‾ ⎸ / ⎸ ⫽////////////⎹⎸\⫽ + // 0⌿-‾‾ ⎹/ ⎹⫽\\\\\\\\\\\\\║/⫽ 0⌿-‾‾ |/ |⫽//////////////║⫽ + // *--------------* x 0═══════════════1 x *--------------* x {2->3}═════════{1->2} x float lowerCoordinateValue = -5.0; float upperCoordinateValue = 5.0; @@ -1569,115 +1343,89 @@ TEST_F(StaircaserTest, transformTriangleIntoTwoSurfaces) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - EXPECT_EQ(resultMesh.groups[0].elements.size(), 2); - EXPECT_EQ(resultMesh.groups[1].elements.size(), 2); - - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - - EXPECT_TRUE(resultMesh.groups[0].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[0].elements[1].isQuad()); - EXPECT_TRUE(resultMesh.groups[1].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[1].elements[1].isQuad()); - + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } TEST_F(StaircaserTest, transformTriangleWithEquidistantEdges) { - // T0 *-------------1 *-----------{1->0} T2 2-------------* {2->3}-----------* - // /| ⫽║ /| /║ /║\ /| /║ /| - // / | ╱/⎹| / | / ║ /⎹| \ / | / ║ / | - // z/ | ╱ / || z/ | / ║ z/ || \ / | z/ ║ / | - // *---┼------⌿--* || -> *---┼----------* ║ *--┼┼---------* | -> *---╫----------* | - // | |y ╱ | ⎹ | ⎸ | -> | ║ | | |y \ | | | ║ y -> | | - // | *--╱------┼-┼-* ⎸{0.33->4}=====╪{1.5|0.66->1} | ⎸ *-----\---┼---* {1.5|2.33->2}===╪{2.66->4} - // | / ╱ |⎹ / ⎸⩘ ⫽///////////|//⫽ ⎹ ⎸ / \ ⎸ / ⎹ ⩘⫽///////////|//⫽ - // | /╱ |⎸/ ⎸/⫽////////////|/⫽ / ⎹| / \ | / ⎹/⫽////////////⎹/⫽/ - // |⫽ ║/ ⎸⫽/////////////|⫽ ⩗ ║/ \⎸/ |⫽/////////////|⫽ ⩗ - // 0=============2 x {0->3}============2 x 1=============0 x 1==============0 x + // T0 *--------------1 *-----------{1->0} T2 2-------------* {2->3}----------* + // /| ⫽⎹⎸ /⎸ /⎹⎸ /⎹⎸\ /| /⎹⎸ /| + // / | ╱/ ⎹| / | / ║ / || \ / | / ║ / ⎹ + // z/ | ╱ / ⎸| z/ | / ║ z/ | | \ / | z/ ║ / ⎹ + // *---┼------/--* | | -> *---┼----------* ║ *--┼-┼---------* | -> *---╫----------* ⎹ + // | |y ╱ | ⎹ | ⎸ | -> | ║ | | |y \ | | | ║ y -> | ⎹ + // | *--╱------┼-┼--* ⎸{0.33->4}═════╪{1.5|0.66->1} | ⎸ *-----\---┼---* {1.5|2.33->2}══╪{2.66->4} + // | / ╱ ⎹⎹ / ⎸⩘ ⫽//////////⎹///⫽ ⎸⎸ / \ ⎸ / | ⩘⫽//////////|///⫽ + // | /╱ ⎹⎸ / ⎸/⫽////////////⎸/⫽ / |⎸/ \ | / |/⫽////////////|/⫽ / + // |⫽ ║/ ⎹⫽/////////////|⫽ ⩗ ║/ \⎸/ ⎹⫽/////////////|⫽ ⩗ + // 0═════════════2 x {0->3}═════════════2 x 1═════════════0 x 1══════════════0 x // <- <- // - // T1 *-------------1 *-----------{1->2} T2 0-------------* 0--------------* - // /| ⫽║ /| /║ /║\ /| / ║ /| - // / | ╱/⎹| / | / ║ /⎹| \ / | / ║ / | - // z/ | ╱ / || z/ | / ║ z/ || \ / | z / ║ / | - // *---┼------⌿--* || -> *---┼----------* ║ *--┼┼---------* | -> *----╫---------* | - // | |y ╱ | ⎹ | ⎸ | <- | ║ | | |y \ | | | ║ y <- | | - // | *--╱------┼-┼-* ⎸{1.66->4}=====╪{0.5|1.33->1} | ⎸ *-----\---┼---* {0.5|2.66->1}==={2.33->4} - // | / ╱ |⎹ / ⎸ /⫽\\\\\\\\\\\|\\⫽ ⎹ ⎸ / \ ⎸ / ⎹ /⫽\\\\\\\\\\\|\\\⫽ - // | /╱ |⎸/ ⎸⩗⫽\\\\\\\\\\\\|\⫽ ⩘ |⎸/ \ | / |⩗⫽\\\\\\\\\\\\\⎸\⫽⩘ - // |⫽ ║/ ⎸⫽\\\\\\\\\\\\\|⫽ / ║/ \⎸/ |⫽\\\\\\\\\\\\\\|⫽ / - // 1=============0 x {2->3}============0 x 1=============2 x {1->2}=========={2->3} x + // T1 *--------------1 *-----------{1->2} T2 0-------------* 0--------------* + // /| ⫽⎹⎸ /⎸ /⎹⎸ /║\ /| / ║ /| + // / | ╱/ ⎹| / | / ║ /⎹| \ / | / ║ / | + // z/ | ╱ / ⎸| z/ | / ║ z/ || \ / | z / ║ / | + // *---┼------/--* | | -> *---┼----------* ║ *--┼┼---------* | -> *----╫---------* | + // | |y ╱ | ⎹ | ⎸ | <- | ║ | | |y \ | | | ║ y <- | | + // | *--╱------┼-┼--* ⎸{1.66->4}═════╪{0.5|1.33->1} | ⎸ *-----\---┼---* {0.5|2.66->1}═══{2.33->4} + // | / ╱ ⎹⎹ / ⎸ /⫽\\\\\\\\\\⎹\\\⫽ ⎹ ⎸ / \ ⎸ / ⎹ /⫽\\\\\\\\\\\|\\\⫽ + // | /╱ ⎹⎸ / ⎸⩗⫽\\\\\\\\\\\|\\⫽⩘ |⎸/ \ | / |⩗⫽\\\\\\\\\\\\\⎸\⫽⩘ + // |⫽ ║/ ⎹⫽\\\\\\\\\\\\\|⫽ / ║/ \⎸/ |⫽\\\\\\\\\\\\\\|⫽ / + // 2═════════════0 x {0->3}═════════════2 x 1═════════════2 x {1->2}══════════{2->3} x // -> -> // - // T4 *--------------* *-----------{1->4} T6 1-------------* {1->0}====={0.66|1.5->1} + // T4 *--------------* *-----------{1->4} T6 1-------------* {1->0}═════{0.66|1.5->1} // /| /| /| ⩘ ⫽║ /\⟍ /⎹ /| ⫽║ // / | / | / | / ⫽/║ / |\ ⟍ / ⎸ / | ⩘ ⫽/║ // z / | / | z/ | ⫽//║ | z/ | \ ⟍ / ⎸ z/ | / ⫽//║ ⎸ - // 1===|----------* | -> {1->2}╪===={0.5|1.33->1}v *---┼--\-----*⟍ ⎹ -> *----┼------{0.33->4}║ v + // 1═══|----------* | -> {1->2}╪════{0.5|1.33->1}v *---┼--\-----*⟍ ⎹ -> *----┼------{0.33->4}║ v // | ⟍ |‾‾⎻⎻⎼⎼__ | | | | -> ║///║ | ⎸ \ ⎹ ⟍ ⎸ ⎹ ⎸ ║////║ - // | *⟍-------⎺⎺|===2 | *----------║/{2->3} | *----\--⎻┼---2 ⎸ *----------║////2 + // | *⟍-------⎺⎺|═══2 | *----------║/{2->3} | *----\--⎻┼---2 ⎸ *----------║////2 // | / ⟍ | ⫽ ⎸ / ᐱ ║//⫽ | / \ ⎸ ⫽ | / ᐱ ║///⫽ // | / ⟍ | ⫽ ⎸ / ⎹ ║/⫽ / ⎹ / \ | ⫽ ⎹ / ⎹ ║//⫽ / // |/ ⟍ |⫽ ⎸/ ║⫽ ⩗ |/ \⎸⫽ |/ ║⫽ ⩗ // *--------------0 x *--------------0 x *-------------0 x *-------------{0->3} x // // - // T4 *--------------* *----------{1.33->4} T6 2-------------* {2->3}====={2.33|1.5->2} + // T4 *--------------* *----------{1.33->4} T6 2-------------* {2->3}═════{2.33|1.5->2} // /| /| /| ⫽║ /\⟍ /⎹ /| / ⫽/║ // / | / | / | / ⫽\║ /| \ ⟍ / ⎸ / | ⩗ ⫽//║ // z / | / | z/ | ⩗ ⫽\\║ ᐱ z/ ⎸ \ ⟍ / | z/ ⎹ ⫽///║ ᐱ - // 2===|----------* | -> 2===╪===={2.5|1.66->3}⎹ *---┼---\-----*⟍ | -> *---┼----{2.66->4}║ ⎸ + // 2═══|----------* | -> 2═══╪════{2.5|1.66->3}⎹ *---┼---\-----*⟍ | -> *---┼----{2.66->4}║ ⎸ // | ⟍ |‾‾⎻⎻⎼⎼__ | | | | -> ║\\\║ ⎹ | \ | ⟍ ⎸ ⎹ ⎸ ║////║ - // | *⟍-------⎺⎺|===1 | *-----------║\\\1 ⎹ *-----\---┼----1 ⎸ *--------║////1 + // | *⟍-------⎺⎺|═══1 | *-----------║\\\1 ⎹ *-----\---┼----1 ⎸ *--------║////1 // | / ⟍ | ⫽ ⎸ / | ║\\⫽ | / \ ⎸ ⫽ | / ⎹ ║///⫽ // | / ⟍ | ⫽ ⎸ / V ║\⫽ ⩘ | / \⎹ ⫽ | / V ║/⫽ ⩘ // |/ ⟍ |⫽ ⎸/ ║⫽ / |/ \⎸⫽ |/ ║⫽ / // *--------------0 x *-------------0 x *-------------0 x *-------------0 x // // -> -> - // T8 *------------* {1.33->4}===={1.66|2.5->3} T10 *------------* {1.5|2.33->2}======{2.66->4} + // T8 *------------* {1.33->4}════{1.66|2.5->3} T10 *------------* {1.5|2.33->2}══════{2.66->4} // /| /| /║╱╱╱╱╱╱╱╱╱╱╱╱╱⫻║ /| /| ⫽ ║╱╱╱╱╱╱╱╱╱╱╱╱⫽║ // / | / | /ᐱ║╱╱╱╱╱╱╱╱╱╱╱╱⫻╱║ / ⎸ / | ⫽ᐱ║╱╱╱╱╱╱╱╱╱╱╱⫽╱║ // z / | / | z/ |║╱╱╱╱╱╱╱╱╱╱╱⫻╱╱║ ⎸ / | / ⎸ z⫽ ⎸║/////////╱⫽╱╱║ | // 0---┼-------=2 | -> *---║⌿⌿⌿⌿⌿⌿2╱╱╱║ v 2==__--------* | -> {2->3}--║⌿⌿⌿⌿⌿*╱╱╱╱║ v // | | __⎼⎼‾‾ | ⟍ | ⎸ ║╱╱╱╱╱╱╱╱╱╱⎹╱╱╱╱║ | ⟍ ⎸‾‾‾⎼⎼___| | ⎸ ║╱╱╱╱╱╱╱╱╱⎸╱╱╱╱║ - // | 1≡=======╪===0 | 1==========╪====0 | 1========|‾‾≡0 | 1=========╪====0 + // | 1≡═══════╪═══0 | 1══════════╪════0 | 1════════|‾‾≡0 | 1═════════╪════0 // | / | / | / <- | / | / | / | / <- | / // | / | / | / | / | / | / | / | / // |/ |/ |/ | / |/ |/ | / | / // *------------* x *--------------* x *------------* x *--------------* x // // <- <- - // T9 *------------* {1.66->4}===={0.5|1.33->1} T11 *------------* {1.5|0.33->1}======{0.33->4} + // T9 *------------* {1.66->4}════{0.5|1.33->1} T11 *------------* {1.5|0.33->1}══════{0.33->4} // /| /| /║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍⫽║ /| /⎸ ⫽║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍/║ // / | / | /|║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍⫽⟍║ / | / ⎸ ⫽|║⟍⟍⟍⟍⟍⟍⟍⟍⟍/⟍║ // z / | / | z/ v║⟍⟍⟍⟍⟍⟍⟍⟍⟍⫽⟍⟍║ ᐱ / ⎸ / | z⫽ v║⟍⟍⟍⟍⟍⟍⟍⟍/⟍⟍║ ᐱ // 0---┼-------=1 | -> *---║⍀⍀⍀⍀{1->2}⟍\║ | 1==__--------* | -> {1->0}-║⍀⍀⍀⍀⍀⍀*⟍⟍║ | // | | __⎼⎼‾‾ | ⟍ ⎸ ⎸ ║⟍⟍⟍⟍⟍⟍⟍⟍⎸⟍⟍⟍║ | ⟍ ⎸‾‾‾⎼⎼___⎸ ⎸ ⎹ ║⟍⟍⟍⟍⟍⟍⟍⟍|⟍⟍║ - // | 2≡=======╪===0 |{2->3}========╪====0 ⎹ 2========|‾‾≡0 | 2==========╪={0->3} + // | 2≡═══════╪═══0 |{2->3}════════╪════0 ⎹ 2════════|‾‾≡0 | 2══════════╪={0->3} // | / | / | / -> | / ⎹ / | / | / -> | / // | / | / | / | / ⎹ / | / | / | / // |/ |/ |/ | / ⎹/ |/ |/ |/ @@ -1690,11 +1438,11 @@ TEST_F(StaircaserTest, transformTriangleWithEquidistantEdges) // z / | / | z/ | / | z / | ⟋ ╱ / | z/ ⎹ / ║ // *---┼-------=2 | -> *----┼---------2 | *---┼---⌿--⌿-* ⎸ -> *----┼----------* ║ // | | __⎼⎼‾‾⟋| ⎸ | ⎸ -> ║ | ⎹ ⎸⟋ ╱ ⎹ ⎸ ⎹ ⎹ -> ⎸ ║ - // | 1=----⌿-┼----* ⎹ 1=========║{1.33->4} | 0---╱------┼----* | 0========={0.5|1.33->1} + // | 1=----⌿-┼----* ⎹ 1═════════║{1.33->4} | 0---╱------┼----* | 0═════════{0.5|1.33->1} // | ⫽ ⟋ ⎸ / ⎸ ⩘⫽//////////║//⫽ | ⫽ ╱ ⎹ / ⎸⩘ ⫽//////////|//⫽ // | ⫽ ⟋ ⎸ / ⎸/⫽///////////║/⫽ / | ⫽╱ ⎹ / ⎸/⫽///////////⎹/⫽ / // |⫽⟋ ⎹ / ⎸⫽////////////║⫽ ⩗ |⫻ |/ |⫽////////////⎹⫽ ⩗ - // 0------------* x 0========{2.5|1.66->3} x 2--------------* x {2->3}========{1.66->4} x + // 0------------* x 0════════{2.5|1.66->3} x 2--------------* x {2->3}════════{1.66->4} x // <- // // @@ -1705,39 +1453,39 @@ TEST_F(StaircaserTest, transformTriangleWithEquidistantEdges) // z / | / | z/ | / | z / | ⟋ ╱ / ⎸ z/ ⎹ / ║ // *---┼-------=3 | -> *----┼--------{1->2}| *---┼---⌿--⌿-* | -> *----┼--------* ║ // | | __⎼⎼‾‾⟋| ⎸ | | <- ║ ⎸ | ⎸⟋ ╱ ⎹ | ⎹ ⎹ <- ⎹ ║ - // | 2=----⌿-┼----* ⎹ {2->3}=======║{1.66->4} ⎹ 0---╱------┼---* | 0========╪{2.5|1.66->3} + // | 2=----⌿-┼----* ⎹ {2->3}═══════║{1.66->4} ⎹ 0---╱------┼---* | 0════════╪{2.5|1.66->3} // | ⫽ ⟋ ⎸ / ⎸ /⫽\\\\\\\\\\\║\\⫽ ⩘ | ⫽ ╱ ⎹ / ⎹ /⫽\\\\\\\\\|\\⫽ ⩘ // | ⫽ ⟋ ⎸ / ⎸⩗⫽\\\\\\\\\\\\║\⫽ / | ⫽╱ ⎹ / ⎹ ⩗⫽\\\\\\\\\\|\⫽ / // |⫽⟋ ⎹ / ⎸⫽\\\\\\\\\\\\\║⫽ |⫻ ⎹/ ⎹ ⫽\\\\\\\\\\\|⫽ - // 0------------* x 0========{0.5|1.33->1} x 1--------------* x 1========{1.33->4} x + // 0------------* x 0════════{0.5|1.33->1} x 1--------------* x 1════════{1.33->4} x // -> -> // // <- - // T16 *-------------* {2.5|1.66->3}====={1.33->4} T18 *-------------* *------------* + // T16 *-------------* {2.5|1.66->3}═════{1.33->4} T18 *-------------* *------------* // /| /| /⫽\\\\\\\\\\\\⫽⎹ /| /| /⎹ /| // / | / | ⩗⫽\\\\\\\\\\\\⫽⩘| / ⎹ /⎹ / | / ⎸ // z/ | / | z⫽\\\\\\\\\\\\⫽ /⎹ z/ ⎸ / | z/ ⎸ -> / | - // 0============≡1 | -> 0============1 ⎸ 1===╪---------* ⎹ -> 1===╧==={1.33->4}⎹ + // 0════════════≡1 | -> 0════════════1 ⎸ 1═══╪---------* ⎹ -> 1═══╧═══{1.33->4}⎹ // | ⟍ |y__⎼⎼‾‾ ⎸ ⎸ | ║ -> | ⎸ ║ y|‾‾‾⎼⎼⎼___| ⎹ ᐱ ║╱╱╱╱╱╱╱╱╱╱╱╱║ | // | 2=--------┼---* | 2--------┼----* ║ *---------╪=≡≡2 | ║╱╱╱╱╱╱╱╱╱╱╱╱╟---2 // | / | / | / | / ║ / _⎼┼‾ / ║╱╱╱╱╱╱╱╱╱╱╱╱║| ⫽ // | / | / | / | / ║ / __⎼‾‾ | / ║╱╱╱╱╱╱╱╱╱╱╱╱║v⫽ // |/ |/ |/ | / ║/ _⎼⎼‾ |/ ║╱╱╱╱╱╱╱╱╱╱╱╱║⫽ - // *-------------* x *------------* x 0=‾-----------* x 0======{2.5|1.66->3} x + // *-------------* x *------------* x 0=‾-----------* x 0══════{2.5|1.66->3} x // <- // // <- - // T17 *-------------* {0.5|1.33->1}====={1.66->4} T19 *--------------* *-------------* + // T17 *-------------* {0.5|1.33->1}═════{1.66->4} T19 *--------------* *-------------* // /| /| ⩘⫽\\\\\\\\\\\\⫽⎸ / ⎸ /| / ⎸ /⎹ // / | / | /⫽\\\\\\\\\\\\⫽/⎸ / | / | / ⎸ / ⎹ // z/ | / | z⫽\\\\\\\\\\\\⫽⩗ ⎸ z/ ⎸ / ⎸ z/ ⎸ <- / ⎹ - // 0============≡2 | -> 0=========={2->3}| 2===╪---------* | -> {2->3}╧==={1.66->4} | + // 0════════════≡2 | -> 0══════════{2->3}| 2═══╪---------* | -> {2->3}╧═══{1.66->4} | // | ⟍ |y__⎼⎼‾‾ ⎸ ⎸ | ║ -> | | ║ y⎸‾‾‾⎼⎼⎼___| | ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍║ | // | 1=--------┼---* |{1->2}------┼---* ║ *---------╪=≡≡1 | ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍╟-{1->2} // | / | / | / | / ║ / _⎼┼‾ / v ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍║ᐱ ⫽ // | / | / | / | / ║ / __⎼‾‾ | / ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍║|⫽ // |/ |/ |/ |/ ║/ _⎼⎼‾ |/ ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍║⫽ - // *-------------* x *------------* x 0=‾-----------* x 0======{0.5|1.33->1} x + // *-------------* x *------------* x 0=‾-----------* x 0══════{0.5|1.33->1} x // <- -> float lowerCoordinateValue = -5.0; @@ -1876,107 +1624,42 @@ TEST_F(StaircaserTest, transformTriangleWithEquidistantEdges) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - EXPECT_EQ(resultMesh.groups[0].elements.size(), 2); - EXPECT_EQ(resultMesh.groups[1].elements.size(), 2); - EXPECT_EQ(resultMesh.groups[2].elements.size(), 2); - - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - - EXPECT_TRUE(resultMesh.groups[0].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[0].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[1].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[1].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[2].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[2].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[3].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[3].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[4].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[4].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[5].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[5].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[6].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[6].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[7].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[7].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[8].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[8].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[9].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[9].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[10].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[10].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[11].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[11].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[12].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[12].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[13].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[13].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[14].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[14].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[15].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[15].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[16].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[16].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[17].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[17].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[18].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[18].elements[1].isLine()); - EXPECT_TRUE(resultMesh.groups[19].elements[0].isQuad()); - EXPECT_TRUE(resultMesh.groups[19].elements[1].isLine()); - + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } -} - TEST_F(StaircaserTest, transformTriangleWithDiagonalsPreventingHexagonOfDeath) { // - // T0 *-{2}---------* {2->4}========={0.33->1} + // T0 *-{2}---------* {2->4}═════════{0.33->1} // /| |\ /| / ⫽\\\\\\\\\\\\\⫽/║ // / | ⎹ \ / | ⩗ ⫽\\\\\\\\\\\\\⫽//║ // z/ | ⎹ \ / | z⫽\\\\\\\\\\\\\⫽///║⎹ - // *---┼---┼{0}--* | -> {2.5->5}=========={0}//║ v + // *---┼---┼{0}--* | -> {2.5->5}══════════{0}//║ v // | |y ⎹/ | | ⎸ ║//////////|///║ - // | *--{1}----┼---* {1->3}=========╪=={0.66->2} + // | *--{1}----┼---* {1->3}═════════╪=={0.66->2} // | / | / ⎸ / <- | / // | / | / ⎸ / | / // |/ |/ ⎸/ |/ // *-------------* x *--------------* x // // <- - // T1 *--------{1}--* {2->4}=========={1->3} + // T1 *--------{1}--* {2->4}══════════{1->3} // /| ⟋ / /| / ⫽\\\\\\\\\\\\\⫽║ // / | ⟋ / / | ⩗ ⫽\\\\\\\\\\\\\⫽\║ // z/ ⟋ / / | z⫽\\\\\\\\\\\\\⫽\\║ ^ - // *{2}┼-----⌿--* | -> {2.5->5}=========={6}\\║ | + // *{2}┼-----⌿--* | -> {2.5->5}══════════{6}\\║ | // | | |y / | | ║\\\\\\\\\\\\\\║\\\║ // | ⎹ *---⌿----┼---* ║\\\\\\\\\\\\\\║\{0.66->2} // | | / | / | ║\\\\\\\\\\\\\\║\\⫽ ⩘ // | / ⎸ / | / v ║\\\\\\\\\\\\\\║\⫽ / // |/ |/ |/ ║\\\\\\\\\\\\\\║⫽ - // *--{0}--------* x {0}========={0.33->1} x + // *--{0}--------* x {0}═════════{0.33->1} x // -> // <- // T2 *---------------* {1->3}-------------* @@ -1984,14 +1667,14 @@ TEST_F(StaircaserTest, transformTriangleWithDiagonalsPreventingHexagonOfDeath) // /⎹|\ / | ⩘ ⫽/║ / | // / ⎹| \ / | / ⫽//║| / | // z/ ⎹| \ / | z⫽///║V <- / | - // *---⎹┼---⍀-----* | -> {0.66->2}======={0.33->1}| + // *---⎹┼---⍀-----* | -> {0.66->2}═══════{0.33->1}| // | {2}y \ | | ║\\\\\\\\\\\\\\\║ | // | *-----⍀---┼----* {2->4}║\\\\\\\\\\\\\\\╟=={2.5->5} // | / ⟍ \ ⎸ / ║\\\\\\\\\\\\\\\║///⫽ // | / ⟍ \ ⎸ / ║\\\\\\\\\\\\\\\║//⫽ / // | / ⟍ \ ⎸ / ║\\\\\\\\\\\\\\\║/⫽ ⩗ // |/ ⟍\⎸/ ║\\\\\\\\\\\\\\\║⫽ - // *--------------{0} x {6}============={0} x + // *--------------{0} x {6}═════════════{0} x // // <- // T3 *---------------* {0.66->2}-----------* @@ -1999,29 +1682,29 @@ TEST_F(StaircaserTest, transformTriangleWithDiagonalsPreventingHexagonOfDeath) // / | / / | ⩘ ⫽/║ / ⎸ // / | / / | / ⫽//║| / ⎸ // z/ | {0}----/ | z⫽///║V <- / ⎸ - // *----┼---⌿┼-----* | -> {0.33->1}=========={0} | + // *----┼---⌿┼-----* | -> {0.33->1}══════════{0} | // | |y / ⎸ | | ║\\\\\\\\\\\\\\\║ | // | *-{1}┼-----┼----* {1->3}║\\\\\\\\\\\\\\\╟={1.33->4} // | / \ ⎸ | / ║\\\\\\\\\\\\\\\║///⫽ // | / {2}-----┼--/ ║\\\\\\\\\\\\\\\║//⫽ / // | / | / ║\\\\\\\\\\\\\\\║/⫽ ⩗ // |/ |/ ║\\\\\\\\\\\\\\\║⫽ - // *-------------- * x {6}============{1.66->5} x + // *-------------- * x {6}════════════{1.66->5} x // // <- - // T4 *--{2}----------* {1->3}========={0.66->2} + // T4 *--{2}----------* {1->3}═════════{0.66->2} // /| ║ /| ⫽///////////////⫽| // / | || / | ⫽///////////////⫽ | // / | | | / | ⫽///////////////⫽ ⩘| // z/ |⎹ | / | z⫽///////////////⫽ / | - // *----{1}-┼------* | -> | {6}========={0.33->1} ⎹ + // *----{1}-┼------* | -> | {6}═════════{0.33->1} ⎹ // | y|| ⎸ | | V ║\\\\\\\\\\\\\\\║ ⎹ // | *-┼--┼-----┼----* {1.33->4}║\\\\\\\\\\\\\\\╟----* // | / | | | / / ║\\\\\\\\\\\\\\\║^ / // | / || | / ⩗ ║\\\\\\\\\\\\\\\║| / / // | / ║ | / ║\\\\\\\\\\\\\\\║ / ⩗ // |/ {0}----┼/ ║\\\\\\\\\\\\\\\║/ - // *-------------- * x {1.66->5}============={0} x + // *-------------- * x {1.66->5}═════════════{0} x // -> float lowerCoordinateValue = -5.0; @@ -2096,39 +1779,12 @@ TEST_F(StaircaserTest, transformTriangleWithDiagonalsPreventingHexagonOfDeath) auto resultMesh = Staircaser{ mesh }.getMesh(); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - ASSERT_EQ(resultGroup.elements.size(), expectedGroup.size()); - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - EXPECT_EQ(resultElement.isLine(), expectedElement.isLine()); - EXPECT_EQ(resultElement.isNode(), expectedElement.isNode()); - EXPECT_EQ(resultElement.isNone(), expectedElement.isNone()); - EXPECT_EQ(resultElement.isTriangle(), expectedElement.isTriangle()); - EXPECT_EQ(resultElement.isTetrahedron(), expectedElement.isTetrahedron()); - EXPECT_EQ(resultElement.isQuad(), expectedElement.isQuad()); - - ASSERT_EQ(resultElement.vertices.size(), resultElement.vertices.size()); - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertElementsListEquals(expectedElements[g], resultMesh.groups[g].elements, g); } } @@ -2180,28 +1836,10 @@ TEST_F(StaircaserTest, selectiveStructurerWithEmptySetOfCells) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); - ASSERT_EQ(resultMesh.groups.size(), 1); - ASSERT_EQ(resultMesh.groups[0].elements.size(), expectedElements.size()); - - for (std::size_t i = 0; i < resultMesh.coordinates.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - ASSERT_TRUE(resultMesh.groups[0].elements[0].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[1].isLine()); - - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } @@ -2215,7 +1853,7 @@ TEST_F(StaircaserTest, modifyCoordinateOfASpecificCell) // | | _-‾ | | | _-‾ | // | | _-‾ | | | _-‾ | // | 0-------1-‾ | | | _-‾ | - // *-------------*-------------* 0===============1‾--------------* + // *-------------*-------------* 0═══════════════1‾--------------* // float lowerCoordinateValue = -5.0; @@ -2254,28 +1892,10 @@ TEST_F(StaircaserTest, modifyCoordinateOfASpecificCell) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); - ASSERT_EQ(resultMesh.groups.size(), 1); - ASSERT_EQ(resultMesh.groups[0].elements.size(), expectedElements.size()); - - for (std::size_t i = 0; i < resultMesh.coordinates.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - ASSERT_TRUE(resultMesh.groups[0].elements[0].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[1].isLine()); - - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } TEST_F(StaircaserTest, structureMoreThanOneCell) @@ -2294,7 +1914,7 @@ TEST_F(StaircaserTest, structureMoreThanOneCell) // | | _-‾ | | | _-‾ | // | | _-‾ | | | _-‾ | // | 0-------1-‾ | | | _-‾ | - // *-------------*-------------* 0===============1‾--------------* + // *-------------*-------------* 0═══════════════1‾--------------* // float lowerCoordinateValue = -5.0; @@ -2342,42 +1962,22 @@ TEST_F(StaircaserTest, structureMoreThanOneCell) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(cellSet.size(), 2); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); - ASSERT_EQ(resultMesh.groups.size(), 1); - ASSERT_EQ(resultMesh.groups[0].elements.size(), expectedElements.size()); - - for (std::size_t i = 0; i < resultMesh.coordinates.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - ASSERT_TRUE(resultMesh.groups[0].elements[0].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[1].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[2].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[3].isLine()); - - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; + ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } TEST_F(StaircaserTest, verifyOrderInSelectiveStructurer) { - // *-------------*-------------* (4->3)==========(3->2)-------------* + // *-------------*-------------* (4->3)══════════(3->2)-------------* // | 4-------3-_ | | | ‾-_ | // | | ‾-_ | | | ‾-_ | // | | ‾--_2 | -> | | ‾(2->4) | // | | _-‾ | | | _- | // | | _-‾ | | | _-‾ | // | 0-------1-‾ | | | _-‾ | - // *-------------*-------------* 0===============1‾--------------* + // *-------------*-------------* 0═══════════════1‾--------------* // float lowerCoordinateValue = -5.0; @@ -2424,41 +2024,22 @@ TEST_F(StaircaserTest, verifyOrderInSelectiveStructurer) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); - ASSERT_EQ(resultMesh.groups.size(), 1); - ASSERT_EQ(resultMesh.groups[0].elements.size(), expectedElements.size()); - - for (std::size_t i = 0; i < resultMesh.coordinates.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - ASSERT_TRUE(resultMesh.groups[0].elements[0].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[1].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[2].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[3].isLine()); - - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; + ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } TEST_F(StaircaserTest, structureSpecificTriangles) { - // *-------------*-------------* (4->3)==========(3->2)-------------* + // *-------------*-------------* (4->3)══════════(3->2)-------------* // | 4-------3-_ | ║///////////////║ ‾-_ | // | | /║ ‾-_ | ║///////////////║ ‾-_ | // | | / ║ ‾--_2 | -> ║///////////////║ ‾(2->4) | // | | / ║ _-‾ | ║///////////////║ _- | // | |/ ║ _-‾ | ║///////////////║ _-‾ | // | 0-------1-‾ | ║///////////////║ _-‾ | - // *-------------*-------------* 0===============1‾--------------* + // *-------------*-------------* 0═══════════════1‾--------------* // float lowerCoordinateValue = -5.0; @@ -2506,39 +2087,19 @@ TEST_F(StaircaserTest, structureSpecificTriangles) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); - ASSERT_EQ(resultMesh.groups.size(), 1); - ASSERT_EQ(resultMesh.groups[0].elements.size(), expectedElements.size()); - - for (std::size_t i = 0; i < resultMesh.coordinates.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - ASSERT_TRUE(resultMesh.groups[0].elements[0].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[1].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[2].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[3].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[5].isTriangle()); - - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; + ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } TEST_F(StaircaserTest, selectiveStructurerFillingGapsInFrontier_Split) { - // 4==========_-=2 5=============3 + // 4══════════_-=2 5═════════════3 // ⫽| _-‾‾ /║ ⫽\\\\\\\\\\\\\⫽║ // ⫽ | __-‾ / ║ ⫽\\\\\\\\\\\\\⫽/║ // ⫽ _┼-‾ / |║ ⫽\\\\\\\\\\\\\⫽//║ - // 0=‾-┼---------* |║ 0=============1////║ + // 0=‾-┼---------* |║ 0═════════════1////║ // /║⟍ | /| | ║ /║ | ⫽/║///║ // / ║ ⟍*-------╱-┼-┼-3 / ║ *------╱╱-║///4 // / || / ⟍ ╱ || ⫽ / || / ╱ ╱ ║//⫽ @@ -2605,45 +2166,19 @@ TEST_F(StaircaserTest, selectiveStructurerFillingGapsInFrontier_Split) auto resultMesh = staircaser.getSelectiveMesh(cellSet, Staircaser::GapsFillingType::Split); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); - ASSERT_EQ(resultMesh.groups.size(), 1); - ASSERT_EQ(resultMesh.groups[0].elements.size(), expectedElements.size()); - - for (std::size_t i = 0; i < resultMesh.coordinates.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - ASSERT_TRUE(resultMesh.groups[0].elements[0].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[1].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[2].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[3].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[5].isLine()); - - ASSERT_TRUE(resultMesh.groups[0].elements[6].isQuad()); - ASSERT_TRUE(resultMesh.groups[0].elements[7].isQuad()); - - ASSERT_TRUE(resultMesh.groups[0].elements[8].isTriangle()); - ASSERT_TRUE(resultMesh.groups[0].elements[9].isTriangle()); - - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; + ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } TEST_F(StaircaserTest, selectiveStructurerFillingGapsInFrontier_Insert) { - // 4==========_-=2 5=============3 + // 4══════════_-=2 5═════════════3 // ⫽| _-‾‾ /║ ⫽\\\\\\\\\\\\\⫽║ // ⫽ | __-‾ / ║ ⫽\\\\\\\\\\\\\⫽/║ // ⫽ _┼-‾ / |║ ⫽\\\\\\\\\\\\\⫽//║ - // 0=‾-┼---------* |║ 0=============1////║ + // 0=‾-┼---------* |║ 0═════════════1////║ // /║⟍ | /| | ║ /║⟍ | /║///║ // / ║ ⟍*-------╱-┼-┼-3 / ║ ⟍*-------╱-║///4 // / || / ⟍ ╱ || ⫽ / || / ⟍ ╱ ║//⫽ @@ -2710,48 +2245,22 @@ TEST_F(StaircaserTest, selectiveStructurerFillingGapsInFrontier_Insert) auto resultMesh = staircaser.getSelectiveMesh(cellSet, Staircaser::GapsFillingType::Insert); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); - ASSERT_EQ(resultMesh.groups.size(), 1); - ASSERT_EQ(resultMesh.groups[0].elements.size(), expectedElements.size()); - - for (std::size_t i = 0; i < resultMesh.coordinates.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - ASSERT_TRUE(resultMesh.groups[0].elements[0].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[1].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[2].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[3].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[5].isLine()); - - ASSERT_TRUE(resultMesh.groups[0].elements[6].isQuad()); - ASSERT_TRUE(resultMesh.groups[0].elements[7].isQuad()); - - ASSERT_TRUE(resultMesh.groups[0].elements[8].isTriangle()); - ASSERT_TRUE(resultMesh.groups[0].elements[9].isTriangle()); - - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; + ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } TEST_F(StaircaserTest, selectiveStructurer_SplitLinesWithNeighborTriangle) { - // *----0========2=============5 *---(0->3)==(2->5)========(5->6) + // *----0════════2═════════════5 *---(0->3)==(2->5)════════(5->6) // | ‾-_ ║|\ ║ | ‾-_ ║ \ ║ // | ‾-_ ║ | \ ║ | ‾-_ ║ \ ║ // | - ║ | \ ║ | - ║ \ ║ // | ‾1 | \ ║ | (1->4) \ ║ // | |‾- | \ ║ | ║‾‾--__ \ ║ // | | ‾-_| \ ║ | ║ ‾‾--_\ ║ - // *-------------*-----3=======4 -> *-----------(3->0)========(4->1) + // *-------------*-----3═══════4 -> *-----------(3->0)════════(4->1) // | | \ | | | ║ // | | \ | | | ║ // | | \ | | | ║ @@ -2813,33 +2322,10 @@ TEST_F(StaircaserTest, selectiveStructurer_SplitLinesWithNeighborTriangle) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.coordinates.size(), expectedRelatives.size()); - ASSERT_EQ(resultMesh.groups.size(), 1); - ASSERT_EQ(resultMesh.groups[0].elements.size(), expectedElements.size()); - - for (std::size_t i = 0; i < resultMesh.coordinates.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]); - } - } - - ASSERT_TRUE(resultMesh.groups[0].elements[0].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[1].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[2].isLine()); - ASSERT_TRUE(resultMesh.groups[0].elements[3].isLine()); - - ASSERT_TRUE(resultMesh.groups[0].elements[4].isTriangle()); - ASSERT_TRUE(resultMesh.groups[0].elements[5].isTriangle()); - ASSERT_TRUE(resultMesh.groups[0].elements[6].isTriangle()); - ASSERT_TRUE(resultMesh.groups[0].elements[7].isTriangle()); - - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; + ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } - + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } + +} \ No newline at end of file From 24242e33b6d0690b208a34c44ac1ff604f0bb396 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Wed, 26 Nov 2025 17:54:54 +0100 Subject: [PATCH 10/35] WIP | DRAFT | Adding tests for selective staircasing overlapped --- test/core/StaircaserTest.cpp | 429 +++++++++++++++++------------------ 1 file changed, 211 insertions(+), 218 deletions(-) diff --git a/test/core/StaircaserTest.cpp b/test/core/StaircaserTest.cpp index 6d1aa5e..8646403 100644 --- a/test/core/StaircaserTest.cpp +++ b/test/core/StaircaserTest.cpp @@ -1359,133 +1359,133 @@ TEST_F(StaircaserTest, transformTriangleWithEquidistantEdges) // / | ╱/ ⎹| / | / ║ / || \ / | / ║ / ⎹ // z/ | ╱ / ⎸| z/ | / ║ z/ | | \ / | z/ ║ / ⎹ // *---┼------/--* | | -> *---┼----------* ║ *--┼-┼---------* | -> *---╫----------* ⎹ - // | |y ╱ | ⎹ | ⎸ | -> | ║ | | |y \ | | | ║ y -> | ⎹ - // | *--╱------┼-┼--* ⎸{0.33->4}═════╪{1.5|0.66->1} | ⎸ *-----\---┼---* {1.5|2.33->2}══╪{2.66->4} - // | / ╱ ⎹⎹ / ⎸⩘ ⫽//////////⎹///⫽ ⎸⎸ / \ ⎸ / | ⩘⫽//////////|///⫽ - // | /╱ ⎹⎸ / ⎸/⫽////////////⎸/⫽ / |⎸/ \ | / |/⫽////////////|/⫽ / - // |⫽ ║/ ⎹⫽/////////////|⫽ ⩗ ║/ \⎸/ ⎹⫽/////////////|⫽ ⩗ - // 0═════════════2 x {0->3}═════════════2 x 1═════════════0 x 1══════════════0 x + // | |y ╱ | ⎹ | ⎸ | -> | ║ ⎸ | |y \ | | | ║ y -> | ⎹ + // | *--╱------┼-┼--* ⎸{0.33->4}═════╪{1.5|0.66->1} ⎸ ⎸ *-----\---┼---* {1.5|2.33->2}══╪{2.66->4} + // | / ╱ ⎹⎹ / ⎸⩘ ⫽//////////⎹///⫽ ⎹ ⎸ / \ ⎸ / ⎸ ⩘⫽//////////|///⫽ + // | /╱ ⎹⎸ / ⎸/⫽////////////⎸/⫽ / ⎸⎸/ \ | / |/⫽////////////|/⫽ / + // |⫽ ║/ ⎹⫽/////////////|⫽ ⩗ ║/ \|/ ⎹⫽/////////////|⫽ ⩗ + // 0═════════════2 x {0->3}═════════════2 x 1══════════════0 x 1══════════════0 x // <- <- // - // T1 *--------------1 *-----------{1->2} T2 0-------------* 0--------------* - // /| ⫽⎹⎸ /⎸ /⎹⎸ /║\ /| / ║ /| - // / | ╱/ ⎹| / | / ║ /⎹| \ / | / ║ / | - // z/ | ╱ / ⎸| z/ | / ║ z/ || \ / | z / ║ / | - // *---┼------/--* | | -> *---┼----------* ║ *--┼┼---------* | -> *----╫---------* | - // | |y ╱ | ⎹ | ⎸ | <- | ║ | | |y \ | | | ║ y <- | | - // | *--╱------┼-┼--* ⎸{1.66->4}═════╪{0.5|1.33->1} | ⎸ *-----\---┼---* {0.5|2.66->1}═══{2.33->4} - // | / ╱ ⎹⎹ / ⎸ /⫽\\\\\\\\\\⎹\\\⫽ ⎹ ⎸ / \ ⎸ / ⎹ /⫽\\\\\\\\\\\|\\\⫽ - // | /╱ ⎹⎸ / ⎸⩗⫽\\\\\\\\\\\|\\⫽⩘ |⎸/ \ | / |⩗⫽\\\\\\\\\\\\\⎸\⫽⩘ - // |⫽ ║/ ⎹⫽\\\\\\\\\\\\\|⫽ / ║/ \⎸/ |⫽\\\\\\\\\\\\\\|⫽ / - // 2═════════════0 x {0->3}═════════════2 x 1═════════════2 x {1->2}══════════{2->3} x + // T1 *--------------1 *-----------{1->2} T3 2-------------* {2->3}-----------* + // /| ⫽⎹⎸ /⎸ /⎹⎸ /⎹⎸\ /⎸ /⎹⎸ /| + // / | ╱/ ⎹| / | / ║ / || \ / | / ║ / ⎹ + // z/ | ╱ / ⎸| z/ | / ║ z/ | | \ / | z/ ║ / ⎹ + // *---┼------/--* | | -> *---┼----------* ║ *--┼-┼---------* | -> *----╫----------* ⎹ + // | |y ╱ | ⎹ | ⎸ | <- | ║ | | |y \ | | | ║ y -> | ⎹ + // | *--╱------┼-┼--* ⎸{1.66->4}═════╪{0.5|1.33->1} | ⎸ *------\--┼---* {1.5|2.33->2}═══╪{2.66->4} + // | / ╱ ⎹⎹ / ⎸ /⫽\\\\\\\\\\⎹\\\⫽ || / \ ⎹ / | ⩘⫽\\\\\\\\\\⎹\\\⫽ + // | /╱ ⎹⎸ / ⎸⩗⫽\\\\\\\\\\\|\\⫽⩘ ⎹| / \ ⎸ / ⎹ /⫽\\\\\\\\\\\\|\⫽ / + // |⫽ ║/ ⎹⫽\\\\\\\\\\\\\|⫽ / ⎹⎸/ \⎸/ ⎸⫽\\\\\\\\\\\\\|⫽ ⩗ + // 2═════════════0 x {0->3}═════════════2 x 1══════════════0 x 1═══════════════0 x // -> -> // // T4 *--------------* *-----------{1->4} T6 1-------------* {1->0}═════{0.66|1.5->1} - // /| /| /| ⩘ ⫽║ /\⟍ /⎹ /| ⫽║ - // / | / | / | / ⫽/║ / |\ ⟍ / ⎸ / | ⩘ ⫽/║ - // z / | / | z/ | ⫽//║ | z/ | \ ⟍ / ⎸ z/ | / ⫽//║ ⎸ - // 1═══|----------* | -> {1->2}╪════{0.5|1.33->1}v *---┼--\-----*⟍ ⎹ -> *----┼------{0.33->4}║ v - // | ⟍ |‾‾⎻⎻⎼⎼__ | | | | -> ║///║ | ⎸ \ ⎹ ⟍ ⎸ ⎹ ⎸ ║////║ - // | *⟍-------⎺⎺|═══2 | *----------║/{2->3} | *----\--⎻┼---2 ⎸ *----------║////2 - // | / ⟍ | ⫽ ⎸ / ᐱ ║//⫽ | / \ ⎸ ⫽ | / ᐱ ║///⫽ - // | / ⟍ | ⫽ ⎸ / ⎹ ║/⫽ / ⎹ / \ | ⫽ ⎹ / ⎹ ║//⫽ / - // |/ ⟍ |⫽ ⎸/ ║⫽ ⩗ |/ \⎸⫽ |/ ║⫽ ⩗ + // /| /| /| ⩘ ⫽⎹⎸ /\ ⟍ /⎹ /| ⫽/⎹⎸ + // / | / | / | / ⫽//║ / ⎸\ ⟍ / ⎸ / ⎹ ⩘ ⫽//⎹⎸ + // z / | / | z/ | ⫽///║ | z/ ⎸ \ ⟍ / ⎸ z/ ⎹ / ⫽////║ ⎸ + // 1===|----------* | -> {1->2}╪════{0.5|1.33->1}v *---┼---\-----*⟍ | -> *----┼------{0.33->4}/║ v + // | ⟍ ⎸‾‾⎻⎻⎻⎻--⎼⎼⎼⎼__⎹ | | | -> ║////║ ⎸ | \ ⎹ ⟍| ⎸ | ║/////║ + // | *⟍---------|===2 ⎸ *---------⎹⎸/{2->3} ⎹ *-----\---┼---2 ⎸ *----------║/////2 + // | / ⟍ | ⫽ ⎹ / ᐱ ║///⫽ ⎹ / \ ⎸ ⫽ | / ᐱ ║////⫽ + // | / ⟍ | ⫽ ⎹ / ⎹ ⎹⎸/⫽ / ⎸ / \ | ⫽ ⎹ / ⎹ ⎹⎸//⫽ / + // |/ ⟍ |⫽ ⎹/ ⎹⎸⫽ ⩗ ⎹/ \⫽ |/ ║/⫽ ⩗ // *--------------0 x *--------------0 x *-------------0 x *-------------{0->3} x // // - // T4 *--------------* *----------{1.33->4} T6 2-------------* {2->3}═════{2.33|1.5->2} - // /| /| /| ⫽║ /\⟍ /⎹ /| / ⫽/║ - // / | / | / | / ⫽\║ /| \ ⟍ / ⎸ / | ⩗ ⫽//║ - // z / | / | z/ | ⩗ ⫽\\║ ᐱ z/ ⎸ \ ⟍ / | z/ ⎹ ⫽///║ ᐱ - // 2═══|----------* | -> 2═══╪════{2.5|1.66->3}⎹ *---┼---\-----*⟍ | -> *---┼----{2.66->4}║ ⎸ - // | ⟍ |‾‾⎻⎻⎼⎼__ | | | | -> ║\\\║ ⎹ | \ | ⟍ ⎸ ⎹ ⎸ ║////║ - // | *⟍-------⎺⎺|═══1 | *-----------║\\\1 ⎹ *-----\---┼----1 ⎸ *--------║////1 - // | / ⟍ | ⫽ ⎸ / | ║\\⫽ | / \ ⎸ ⫽ | / ⎹ ║///⫽ - // | / ⟍ | ⫽ ⎸ / V ║\⫽ ⩘ | / \⎹ ⫽ | / V ║/⫽ ⩘ - // |/ ⟍ |⫽ ⎸/ ║⫽ / |/ \⎸⫽ |/ ║⫽ / - // *--------------0 x *-------------0 x *-------------0 x *-------------0 x + // T5 *--------------* *-----------{1->4} T7 2-------------* {2->3}═════{2.33|1.5->2} + // /| /| /| ⫽⎹⎸ /|\⟍ /⎸ /| / ⫽///║ + // / | / | / | / ⫽\⎹⎸ / ⎸ \ ⟍ / ⎸ / ⎸ ⩗ ⫽///⎹⎸ + // z / | / | z/ | ⩗⫽\\\║ ᐱ z/ | \ ⟍ / | z/ | ⫽/////║ ᐱ + // 2===|----------* | -> 2═══╪════{2.5|1.66->3}| *---┼---\-----*⟍ | -> *----┼------{2.66->4}//⎹⎸ ⎸ + // | ⟍ ⎸‾‾⎻⎻⎻⎻--⎼⎼⎼⎼__⎹ | | | -> ⎹⎸\\\\║ ⎹ | \ ⎹ ⟍| | | ║/////⎹⎸ + // | *⟍---------|===1 ⎸ *---------⎹⎸\\\\1 ⎹ *-----\---┼---1 ⎹ *-----------║/////1 + // | / ⟍ | ⫽ ⎹ / | ║\\\⫽ ⎹ / \ | ⫽ | / | ⎹⎸////⫽ + // | / ⟍ | ⫽ ⎹ / V ║\⫽ ⩘ ⎸ / \ ⎸ ⫽ ⎸ / V ║///⫽ ⩘ + // |/ ⟍ |⫽ ⎹/ ║⫽ / |/ \|⫽ |/ ⎹⎸/⫽ / + // *--------------0 x *--------------0 x *--------------0 x *---------------0 x // // -> -> - // T8 *------------* {1.33->4}════{1.66|2.5->3} T10 *------------* {1.5|2.33->2}══════{2.66->4} - // /| /| /║╱╱╱╱╱╱╱╱╱╱╱╱╱⫻║ /| /| ⫽ ║╱╱╱╱╱╱╱╱╱╱╱╱⫽║ - // / | / | /ᐱ║╱╱╱╱╱╱╱╱╱╱╱╱⫻╱║ / ⎸ / | ⫽ᐱ║╱╱╱╱╱╱╱╱╱╱╱⫽╱║ - // z / | / | z/ |║╱╱╱╱╱╱╱╱╱╱╱⫻╱╱║ ⎸ / | / ⎸ z⫽ ⎸║/////////╱⫽╱╱║ | - // 0---┼-------=2 | -> *---║⌿⌿⌿⌿⌿⌿2╱╱╱║ v 2==__--------* | -> {2->3}--║⌿⌿⌿⌿⌿*╱╱╱╱║ v - // | | __⎼⎼‾‾ | ⟍ | ⎸ ║╱╱╱╱╱╱╱╱╱╱⎹╱╱╱╱║ | ⟍ ⎸‾‾‾⎼⎼___| | ⎸ ║╱╱╱╱╱╱╱╱╱⎸╱╱╱╱║ - // | 1≡═══════╪═══0 | 1══════════╪════0 | 1════════|‾‾≡0 | 1═════════╪════0 - // | / | / | / <- | / | / | / | / <- | / - // | / | / | / | / | / | / | / | / - // |/ |/ |/ | / |/ |/ | / | / - // *------------* x *--------------* x *------------* x *--------------* x + // T8 *------------* {1.33->4}════{1.66|2.5->3} T10 *--------------* {1.5|2.33->2}══════{2.66->4} + // /| /| /║╱╱╱╱╱╱╱╱╱╱╱╱╱⫻║ /⎹ /⎹ ⫽║╱╱╱╱╱╱╱╱╱╱╱╱⫽║ + // / | / | /^║╱╱╱╱╱╱╱╱╱╱╱╱⫻ ║ / ⎹ / ⎹ ⫽^║╱╱╱╱╱╱╱╱╱╱╱⫽╱║ + // z / | / | z/ |║╱╱╱╱╱╱╱╱╱╱╱⫻╱ ║ ⎸ z/ ⎹ / ⎹ z ⫽ ⎸║/////////╱⫽╱╱║ | + // 0---┼--------2 | -> *---╫-----------2╱╱⎹⎸ v 2===⎹----------* | -> {2->3}--║---------*╱╱╱⎹⎸ v + // | |__⎼⎼⎼⎼⎻⎻⎻⎻‾‾⎹ ⟍ | | ║╱╱╱╱╱╱╱╱╱╱|╱╱╱⎹⎸ | ⟍‾⎸‾⎻⎻⎻⎻⎻--⎼⎼⎼⎼⎼⎼⎼_⎸__ ⎸ | ║╱╱╱╱╱╱╱╱╱|╱╱╱⎹⎸ + // | 1════════╪═══0 | 1══════════╪════0 | 1══════════╪═══0 | 1═════════╪════0 + // | / | / | / <- | / | / | / | / <- | / + // | / | / | / | / | / | / | / | / + // |/ |/ |/ | / |/ |/ |/ | / + // *------------* x *--------------* x *--------------* x *-------------* x // // <- <- - // T9 *------------* {1.66->4}════{0.5|1.33->1} T11 *------------* {1.5|0.33->1}══════{0.33->4} - // /| /| /║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍⫽║ /| /⎸ ⫽║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍/║ - // / | / | /|║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍⫽⟍║ / | / ⎸ ⫽|║⟍⟍⟍⟍⟍⟍⟍⟍⟍/⟍║ - // z / | / | z/ v║⟍⟍⟍⟍⟍⟍⟍⟍⟍⫽⟍⟍║ ᐱ / ⎸ / | z⫽ v║⟍⟍⟍⟍⟍⟍⟍⟍/⟍⟍║ ᐱ - // 0---┼-------=1 | -> *---║⍀⍀⍀⍀{1->2}⟍\║ | 1==__--------* | -> {1->0}-║⍀⍀⍀⍀⍀⍀*⟍⟍║ | - // | | __⎼⎼‾‾ | ⟍ ⎸ ⎸ ║⟍⟍⟍⟍⟍⟍⟍⟍⎸⟍⟍⟍║ | ⟍ ⎸‾‾‾⎼⎼___⎸ ⎸ ⎹ ║⟍⟍⟍⟍⟍⟍⟍⟍|⟍⟍║ - // | 2≡═══════╪═══0 |{2->3}════════╪════0 ⎹ 2════════|‾‾≡0 | 2══════════╪={0->3} - // | / | / | / -> | / ⎹ / | / | / -> | / - // | / | / | / | / ⎹ / | / | / | / - // |/ |/ |/ | / ⎹/ |/ |/ |/ - // *------------* x *--------------* x *-----------* x *--------------* x + // T9 *------------* {1.66->4}════{0.5|1.33->1} T11 *--------------* {1.5|0.33->1}══════{0.33->4} + // /| /| /║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍⫽║ /⎹ /⎹ ⫽║⟍⟍⟍⟍⟍⟍⟍⟍⟍⫽║ + // / | / | /|║⟍⟍⟍⟍⟍⟍⟍⟍⟍⫽⟍║ / ⎹ / ⎹ ⫽^║⟍⟍⟍⟍⟍⟍⟍⟍⫽⟍║ + // z / | / | z/ v║⟍⟍⟍⟍⟍⟍⟍⟍⫽⟍⟍║ ᐱ z/ ⎹ / | z ⫽ ⎸║⟍⟍⟍⟍⟍⟍⟍⫽⟍⟍║ ᐱ + // 0---┼--------1 | -> *---╫----------2⟍⟍⟍║ | 1===⎹----------* | -> {1->0}--⎹⎸--------*⟍⟍⟍║ | + // | |__⎼⎼⎼⎼⎻⎻⎻⎻‾‾⎹ ⟍ | | ║⟍⟍⟍⟍⟍⟍⟍⎹⟍⟍⟍⎹⎸ ⎸ ⟍‾⎸‾⎻⎻⎻⎻⎻--⎼⎼⎼⎼⎼⎼⎼_⎸__ ⎸ | ║⟍⟍⟍⟍⟍⟍⎹⟍⟍⟍⎹⎸ + // | 2════════╪═══0 |{2->3}════════╪════0 | 2══════════╪═══0 | 1═════════╪═══{0->3} + // | / | / | / -> | / | / | / | / -> | / + // | / | / | / | / | / | / | / | / + // |/ |/ |/ | / |/ |/ |/ | / + // *------------* x *--------------* x *--------------* x *-------------* x // // - // T12 *-------------* *-------------* T14 *--------------1 *-----------{1->2} - // /| / | /| /| /| ⫻ ⎸ /| /║ - // / | / | / | / | / | ⟋ ╱/ | / ⎹ / ║ - // z / | / | z/ | / | z / | ⟋ ╱ / | z/ ⎹ / ║ - // *---┼-------=2 | -> *----┼---------2 | *---┼---⌿--⌿-* ⎸ -> *----┼----------* ║ - // | | __⎼⎼‾‾⟋| ⎸ | ⎸ -> ║ | ⎹ ⎸⟋ ╱ ⎹ ⎸ ⎹ ⎹ -> ⎸ ║ - // | 1=----⌿-┼----* ⎹ 1═════════║{1.33->4} | 0---╱------┼----* | 0═════════{0.5|1.33->1} - // | ⫽ ⟋ ⎸ / ⎸ ⩘⫽//////////║//⫽ | ⫽ ╱ ⎹ / ⎸⩘ ⫽//////////|//⫽ - // | ⫽ ⟋ ⎸ / ⎸/⫽///////////║/⫽ / | ⫽╱ ⎹ / ⎸/⫽///////////⎹/⫽ / - // |⫽⟋ ⎹ / ⎸⫽////////////║⫽ ⩗ |⫻ |/ |⫽////////////⎹⫽ ⩗ - // 0------------* x 0════════{2.5|1.66->3} x 2--------------* x {2->3}════════{1.66->4} x + // T12 *-------------* *-------------* T14 *---------------1 *-----------{1->2} + // /| / | / | /| / | ⟋⫽⎸ /⎹ /⎹⎸ + // / | / | / | / | / | ⟋ ╱/| / | / ║ + // z / | / | z/ | / | z/ | ⟋ ╱ / | z/ | / ║ + // *---┼-------==2 | -> *----┼---------2 | *----┼----⟋---/-* | -> *----┼---------* ║ + // | | _⎼⎼⎼⎼⎻⎻⎻⎻‾⟋⎹ ⎸ ⎸ | -> ║ | | | ⟋ ╱ | | | | -> ⎸ ║ + // | 1=----⟋--┼----* ⎸ 1═════════║{1.33->4} ⎸ 0---╱------┼---* | 0═════════{0.5|1.33->1} + // | ⫽ ⟋ ⎸ / | ⩘⫽//////////⎹⎸//⫽ | ⫽ ╱ ⎸ / | ⩘ ⫽/////////|///⫽ + // | ⫽ ⟋ ⎸ / |/⫽/////////// ║/⫽ / ⎹ ⫽╱ | / ⎹ /⫽///////////⎸/⫽ / + // |⫽⟋ ⎸/ |⫽//////////// ║⫽ ⩗ |⫻ ⎹/ ⎸⫽////////////⎹⫽ ⩗ + // 0-------------* x 0════════{2.5|1.66->3} x 2---------------* x {2->3}════════{1.66->4} x // <- // // // - // T13 *-------------* *--------------* T15 *--------------2 *------------2 - // /| / | /| /| /| ⫻⎹ /⎹ /║ - // / | / | / | / | / | ⟋ ╱/ ⎸ / ⎹ / ║ - // z / | / | z/ | / | z / | ⟋ ╱ / ⎸ z/ ⎹ / ║ - // *---┼-------=3 | -> *----┼--------{1->2}| *---┼---⌿--⌿-* | -> *----┼--------* ║ - // | | __⎼⎼‾‾⟋| ⎸ | | <- ║ ⎸ | ⎸⟋ ╱ ⎹ | ⎹ ⎹ <- ⎹ ║ - // | 2=----⌿-┼----* ⎹ {2->3}═══════║{1.66->4} ⎹ 0---╱------┼---* | 0════════╪{2.5|1.66->3} - // | ⫽ ⟋ ⎸ / ⎸ /⫽\\\\\\\\\\\║\\⫽ ⩘ | ⫽ ╱ ⎹ / ⎹ /⫽\\\\\\\\\|\\⫽ ⩘ - // | ⫽ ⟋ ⎸ / ⎸⩗⫽\\\\\\\\\\\\║\⫽ / | ⫽╱ ⎹ / ⎹ ⩗⫽\\\\\\\\\\|\⫽ / - // |⫽⟋ ⎹ / ⎸⫽\\\\\\\\\\\\\║⫽ |⫻ ⎹/ ⎹ ⫽\\\\\\\\\\\|⫽ - // 0------------* x 0════════{0.5|1.33->1} x 1--------------* x 1════════{1.33->4} x + // T13 *-------------* *--------------* T15 *---------------2 *--------------2 + // /| / | / | /| / | ⟋⫽⎸ / ⎸ /⎹⎸ + // / | / | / | / | / | ⟋ ╱/| / | / ║ + // z / | / | z/ | / | z/ | ⟋ ╱ / | z/ | / ║ + // *---┼-------==1 | -> *----┼--------{1->2}| *----┼----⟋---/-* | -> *----┼---------* ║ + // | | _⎼⎼⎼⎼⎻⎻⎻⎻‾⟋⎹ ⎸ ⎸ | -> ║ | | | ⟋ ╱ | | ⎹ | -> ⎸ ║ + // | 2=----⟋--┼----* ⎸ {2->3}══════║{1.66->4} ⎸ 0---╱------┼---* ⎹ 0═════════{2.5|1.66->3} + // | ⫽ ⟋ ⎸ / | /⫽\\\\\\\\\\\║\\\⫽⩘ | ⫽ ╱ ⎸ / ⎹ / ⫽\\\\\\\\\|\\\⫽ ⩘ + // | ⫽ ⟋ ⎸ / |⩗⫽\\\\\\\\\\\⎹⎸\⫽ / ⎸ ⫽╱ ⎸ / ⎸⩗⫽\\\\\\\\\\\⎸\⫽ / + // |⫽⟋ ⎸/ |⫽\\\\\\\\\\\\\║⫽ |⫻ ⎸/ |⫽\\\\\\\\\\\\⎹⫽ + // 0-------------* x 0════════{0.5|1.33->1} x 1---------------* x 1════════{1.33->4} x // -> -> // // <- - // T16 *-------------* {2.5|1.66->3}═════{1.33->4} T18 *-------------* *------------* - // /| /| /⫽\\\\\\\\\\\\⫽⎹ /| /| /⎹ /| - // / | / | ⩗⫽\\\\\\\\\\\\⫽⩘| / ⎹ /⎹ / | / ⎸ - // z/ | / | z⫽\\\\\\\\\\\\⫽ /⎹ z/ ⎸ / | z/ ⎸ -> / | - // 0════════════≡1 | -> 0════════════1 ⎸ 1═══╪---------* ⎹ -> 1═══╧═══{1.33->4}⎹ - // | ⟍ |y__⎼⎼‾‾ ⎸ ⎸ | ║ -> | ⎸ ║ y|‾‾‾⎼⎼⎼___| ⎹ ᐱ ║╱╱╱╱╱╱╱╱╱╱╱╱║ | - // | 2=--------┼---* | 2--------┼----* ║ *---------╪=≡≡2 | ║╱╱╱╱╱╱╱╱╱╱╱╱╟---2 - // | / | / | / | / ║ / _⎼┼‾ / ║╱╱╱╱╱╱╱╱╱╱╱╱║| ⫽ - // | / | / | / | / ║ / __⎼‾‾ | / ║╱╱╱╱╱╱╱╱╱╱╱╱║v⫽ - // |/ |/ |/ | / ║/ _⎼⎼‾ |/ ║╱╱╱╱╱╱╱╱╱╱╱╱║⫽ - // *-------------* x *------------* x 0=‾-----------* x 0══════{2.5|1.66->3} x + // T16 *------------* {2.5|1.66->3}═════{1.33->4} T18 *---------------* *-------------* + // /⎹ /| / ⫽\\\\\\\\\\\\⫽⎸ /| /⎸ /| /| + // / ⎹ / | ⩗⫽\\\\\\\\\\\\⫽ | / | / ⎹ / ⎸ / ⎸ + // z / ⎹ / | z⫽\\\\\\\\\\\\⫽⩘ | z/ ⎸ / ⎹ z / ⎸ -> / ⎸ + // 0════════════1 | -> 0════════════1 / ⎹ 1===┼----------* ⎸ -> 1═══╧═══{1.33->4} | + // | ⟍y|__⎼⎼⎼⎼⎻⎻⎻⎻‾‾| | | ║ -> | ⎹ ║ y|‾‾⎻⎻⎻⎻--⎼⎼⎼⎼__| ⎸ ᐱ ║╱╱╱╱╱╱╱╱╱╱╱╱║ ⎸ + // | 2--------┼---* | 2--------┼----* ║ *----------┼==≡2 | ║╱╱╱╱╱╱╱╱╱╱╱╱╟----2 + // | / | / | / | / ║ / __-┼‾‾/ ║╱╱╱╱╱╱╱╱╱╱╱╱║| ⫽ + // | / | / | / | / ║ / __-‾‾ | / ║╱╱╱╱╱╱╱╱╱╱╱╱║v ⫽ + // |/ |/ |/ | / ║/__-‾‾ |/ ║╱╱╱╱╱╱╱╱╱╱╱╱║⫽ + // *------------* x *------------* x 0=-------------*x 0══════{2.5|1.66->3} x // <- // // <- - // T17 *-------------* {0.5|1.33->1}═════{1.66->4} T19 *--------------* *-------------* - // /| /| ⩘⫽\\\\\\\\\\\\⫽⎸ / ⎸ /| / ⎸ /⎹ - // / | / | /⫽\\\\\\\\\\\\⫽/⎸ / | / | / ⎸ / ⎹ - // z/ | / | z⫽\\\\\\\\\\\\⫽⩗ ⎸ z/ ⎸ / ⎸ z/ ⎸ <- / ⎹ - // 0════════════≡2 | -> 0══════════{2->3}| 2═══╪---------* | -> {2->3}╧═══{1.66->4} | - // | ⟍ |y__⎼⎼‾‾ ⎸ ⎸ | ║ -> | | ║ y⎸‾‾‾⎼⎼⎼___| | ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍║ | - // | 1=--------┼---* |{1->2}------┼---* ║ *---------╪=≡≡1 | ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍╟-{1->2} - // | / | / | / | / ║ / _⎼┼‾ / v ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍║ᐱ ⫽ - // | / | / | / | / ║ / __⎼‾‾ | / ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍║|⫽ - // |/ |/ |/ |/ ║/ _⎼⎼‾ |/ ║⟍⟍⟍⟍⟍⟍⟍⟍⟍⟍║⫽ - // *-------------* x *------------* x 0=‾-----------* x 0══════{0.5|1.33->1} x + // T17 *------------* {0.5|1.33->1}═════{1.66->4} T19 *---------------* *-------------* + // /⎹ /| / ⫽\\\\\\\\\\\\⫽⎸ /| /⎸ /| /| + // / ⎹ / | ⩗⫽\\\\\\\\\\\\⫽/| / | / ⎹ / ⎸ / ⎸ + // z / ⎹ / | z⫽\\\\\\\\\\\\⫽⩗ | z/ ⎸ / ⎹ z / ⎸ <- / ⎸ + // 0════════════2 | -> 0═════════{2->3} ⎹ 2===┼----------* ⎸ -> {2->3}╧══════{1.66->4} + // | ⟍y|__⎼⎼⎼⎼⎻⎻⎻⎻‾‾| | | ║ -> | ⎹ ║ y|‾‾⎻⎻⎻⎻--⎼⎼⎼⎼__| ⎸ ║⟍⟍⟍⟍⟍⟍⟍⟍⟍║ ⎸ + // | 1--------┼---* |{1->2}------┼----* ║ *----------┼==≡1 | ║⟍⟍⟍⟍⟍⟍⟍⟍⟍╟-{1->2} + // | / | / | / | / ║ / __-┼‾‾/ v ║⟍⟍⟍⟍⟍⟍⟍⟍⟍║ᐱ ⫽ + // | / | / | / | / ║ / __-‾‾ | / ║⟍⟍⟍⟍⟍⟍⟍⟍⟍║|⫽ + // |/ |/ |/ | / ║/__-‾‾ |/ ║⟍⟍⟍⟍⟍⟍⟍⟍⟍║⫽ + // *------------* x *------------* x 0=-------------*x 0══════{0.5|1.33->1} x // <- -> float lowerCoordinateValue = -5.0; @@ -1637,39 +1637,39 @@ TEST_F(StaircaserTest, transformTriangleWithDiagonalsPreventingHexagonOfDeath) { // // T0 *-{2}---------* {2->4}═════════{0.33->1} - // /| |\ /| / ⫽\\\\\\\\\\\\\⫽/║ - // / | ⎹ \ / | ⩗ ⫽\\\\\\\\\\\\\⫽//║ - // z/ | ⎹ \ / | z⫽\\\\\\\\\\\\\⫽///║⎹ - // *---┼---┼{0}--* | -> {2.5->5}══════════{0}//║ v - // | |y ⎹/ | | ⎸ ║//////////|///║ - // | *--{1}----┼---* {1->3}═════════╪=={0.66->2} - // | / | / ⎸ / <- | / - // | / | / ⎸ / | / - // |/ |/ ⎸/ |/ + // /| |\ /| / ⫽\\\\\\\\\\\\\⫽/║ + // / | ⎹ \ / | ⩗⫽\\\\\\\\\\\\\⫽//⎹⎸ + // z/ | ⎹ \ / | z⫽\\\\\\\\\\\\\⫽////║ ⎹ + // *---┼---┼{0}--* | -> {2.5->5}══════════{0}////║ v + // | |y ⎹/ | | ⎸ ║//////////|/////║ + // | *--{1}----┼---* {1->3}═════════╪═{0.66->2} + // | / | / ⎸ / <- | / + // | / | / ⎸ / | / + // |/ |/ ⎸/ | / // *-------------* x *--------------* x // // <- // T1 *--------{1}--* {2->4}══════════{1->3} - // /| ⟋ / /| / ⫽\\\\\\\\\\\\\⫽║ - // / | ⟋ / / | ⩗ ⫽\\\\\\\\\\\\\⫽\║ - // z/ ⟋ / / | z⫽\\\\\\\\\\\\\⫽\\║ ^ - // *{2}┼-----⌿--* | -> {2.5->5}══════════{6}\\║ | - // | | |y / | | ║\\\\\\\\\\\\\\║\\\║ - // | ⎹ *---⌿----┼---* ║\\\\\\\\\\\\\\║\{0.66->2} - // | | / | / | ║\\\\\\\\\\\\\\║\\⫽ ⩘ - // | / ⎸ / | / v ║\\\\\\\\\\\\\\║\⫽ / + // /| ⟋ / /⎸ / ⫽\\\\\\\\\\\\\\⫽║ + // / | ⟋ / / ⎸ ⩗ ⫽\\\\\\\\\\\\\⫽\⎹⎸ + // z/ ⟋ / / ⎸ z⫽\\\\\\\\\\\\\⫽\\\║ ᐱ + // *{2}┼-----/---* | -> {2.5->5}══════════{6}\\\⎹⎸ ⎹ + // | | |y / | | ║\\\\\\\\\\\\\\║\\\\⎹⎸ + // | ⎹ *---/-----┼---* ║\\\\\\\\\\\\\\║\{0.66->2} + // | | / | / | ║\\\\\\\\\\\\\\║\\\⫽ ⩘ + // | / ⎸ / | / v ║\\\\\\\\\\\\\\║\\⫽ / // |/ |/ |/ ║\\\\\\\\\\\\\\║⫽ // *--{0}--------* x {0}═════════{0.33->1} x // -> // <- - // T2 *---------------* {1->3}-------------* - // {1} /| ⫽║ /| - // /⎹|\ / | ⩘ ⫽/║ / | - // / ⎹| \ / | / ⫽//║| / | - // z/ ⎹| \ / | z⫽///║V <- / | - // *---⎹┼---⍀-----* | -> {0.66->2}═══════{0.33->1}| - // | {2}y \ | | ║\\\\\\\\\\\\\\\║ | - // | *-----⍀---┼----* {2->4}║\\\\\\\\\\\\\\\╟=={2.5->5} + // T2 *---------------* {1->3}--------------* + // {1} /| ⫽║ /⎸ + // /⎹|\ / | ⩘ ⫽/⎹⎸ /⎹ + // / ⎹| \ / | / ⫽///║| / ⎸ + // z/ ⎹| \ / | z⫽////║V <- / ⎸ + // *---⎹┼---\------* | -> {0.66->2}═══════{0.33->1} | + // | {2}y \ | | ⎹⎸\\\\\\\\\\\\\\⎹⎸ | + // | *-----\----┼----* {2->4}⎹⎸\\\\\\\\\\\\\\⎹⎸══{2.5->5} // | / ⟍ \ ⎸ / ║\\\\\\\\\\\\\\\║///⫽ // | / ⟍ \ ⎸ / ║\\\\\\\\\\\\\\\║//⫽ / // | / ⟍ \ ⎸ / ║\\\\\\\\\\\\\\\║/⫽ ⩗ @@ -1677,14 +1677,14 @@ TEST_F(StaircaserTest, transformTriangleWithDiagonalsPreventingHexagonOfDeath) // *--------------{0} x {6}═════════════{0} x // // <- - // T3 *---------------* {0.66->2}-----------* - // /| / /| ⫽║ /⎸ - // / | / / | ⩘ ⫽/║ / ⎸ - // / | / / | / ⫽//║| / ⎸ - // z/ | {0}----/ | z⫽///║V <- / ⎸ - // *----┼---⌿┼-----* | -> {0.33->1}══════════{0} | - // | |y / ⎸ | | ║\\\\\\\\\\\\\\\║ | - // | *-{1}┼-----┼----* {1->3}║\\\\\\\\\\\\\\\╟={1.33->4} + // T3 *---------------* {0.66->2}------------* + // /| / /| ⫽║ /⎸ + // / | / / | ⩘ ⫽/⎹⎸ /⎹ + // / | / / | / ⫽///║| / ⎸ + // z/ | {0}----/ | z⫽////║V <- / ⎸ + // *----┼---/-┼----* | -> {0.33->1}══════════{0} | + // | |y / ⎹ | | ║\\\\\\\\\\\\\\\║ | + // | *-{1}┼-----┼----* {1->3}║\\\\\\\\\\\\\\\║═{1.33->4} // | / \ ⎸ | / ║\\\\\\\\\\\\\\\║///⫽ // | / {2}-----┼--/ ║\\\\\\\\\\\\\\\║//⫽ / // | / | / ║\\\\\\\\\\\\\\\║/⫽ ⩗ @@ -1695,16 +1695,16 @@ TEST_F(StaircaserTest, transformTriangleWithDiagonalsPreventingHexagonOfDeath) // T4 *--{2}----------* {1->3}═════════{0.66->2} // /| ║ /| ⫽///////////////⫽| // / | || / | ⫽///////////////⫽ | - // / | | | / | ⫽///////////////⫽ ⩘| + // / | | | / | ⫽///////////////⫽ ⩘⎸ // z/ |⎹ | / | z⫽///////////////⫽ / | - // *----{1}-┼------* | -> | {6}═════════{0.33->1} ⎹ - // | y|| ⎸ | | V ║\\\\\\\\\\\\\\\║ ⎹ - // | *-┼--┼-----┼----* {1.33->4}║\\\\\\\\\\\\\\\╟----* - // | / | | | / / ║\\\\\\\\\\\\\\\║^ / - // | / || | / ⩗ ║\\\\\\\\\\\\\\\║| / / + // *----{1}-┼------* | -> | {6}═════════{0.33->1} | + // | y|| ⎸ | | V ║\\\\\\\\\\\\\\\║ | + // | *-┼--┼-----┼----* {1.33->4}║\\\\\\\\\\\\\\\╟-----* + // | / | | | / / ║\\\\\\\\\\\\\\\║ᐱ / + // | / || | / ⩗ ⎹⎸\\\\\\\\\\\\\\⎹⎸| / / // | / ║ | / ║\\\\\\\\\\\\\\\║ / ⩗ // |/ {0}----┼/ ║\\\\\\\\\\\\\\\║/ - // *-------------- * x {1.66->5}═════════════{0} x + // *---------------* x {1.66->5}═══════════{0} x // -> float lowerCoordinateValue = -5.0; @@ -1788,7 +1788,7 @@ TEST_F(StaircaserTest, transformTriangleWithDiagonalsPreventingHexagonOfDeath) } } -TEST_F(StaircaserTest, selectiveStructurerWithEmptySetOfCells) +TEST_F(StaircaserTest, selectiveStaircaserWithEmptySetOfCells) { // *-------------*-------------* *-------------*-------------* @@ -1797,7 +1797,7 @@ TEST_F(StaircaserTest, selectiveStructurerWithEmptySetOfCells) // | | _2 | -> | | _2 | // | | _-‾ | | | _-‾ | // | | _-‾ | | | _-‾ | - // | 0-------1-‾ | | 0-------1-‾ | + // | 0───────1-‾ | | 0───────1-‾ | // *-------------*-------------* *-------------*-------------* // @@ -1823,20 +1823,13 @@ TEST_F(StaircaserTest, selectiveStructurerWithEmptySetOfCells) Element({1, 2}, Element::Type::Line), }; - Relatives expectedRelatives = { - Relative({ 0.4, 0.1, 0.7 }), // 0 First Segment, First Point - Relative({ 1.0, 0.1, 0.7 }), // 1 First Segment, Second Point - Relative({ 1.8, 0.6, 0.7 }), // 2 Second Segment, Final Point - }; + Relatives expectedRelatives(mesh.coordinates); - Elements expectedElements = { - Element({0, 1}, Element::Type::Line), - Element({1, 2}, Element::Type::Line), - }; + Elements expectedElements(mesh.groups[0].elements); auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + ASSERT_EQ(resultMesh.groups.size(), 1); assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); @@ -1852,8 +1845,8 @@ TEST_F(StaircaserTest, modifyCoordinateOfASpecificCell) // | | _2 | -> | | _2 | // | | _-‾ | | | _-‾ | // | | _-‾ | | | _-‾ | - // | 0-------1-‾ | | | _-‾ | - // *-------------*-------------* 0═══════════════1‾--------------* + // | 0───────1-‾ | | | _-‾ | + // *-------------*-------------* 0═══════════════1=--------------* // float lowerCoordinateValue = -5.0; @@ -1892,28 +1885,28 @@ TEST_F(StaircaserTest, modifyCoordinateOfASpecificCell) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + ASSERT_EQ(resultMesh.groups.size(), 1); assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } -TEST_F(StaircaserTest, structureMoreThanOneCell) +TEST_F(StaircaserTest, staircaseMoreThanOneCell) { // *-------------*-------------* *---------------*-------------(4->3) // | | | | | ║ // | | | | | ║ // | | 4 | -> | | ║ - // | | | | | | ║ - // | | | | | | ║ - // | | | | | | ║ + // | | │ | | | ║ + // | | │ | | | ║ + // | | │ | | | ║ // *-------------*---------3---* *---------------*-------------(3->2) - // | | | | | | ║ - // | | | | | | |‾| + // | | │ | | | ║ + // | | │ | | | |‾| // | | _2 | -> | | (2->4)| // | | _-‾ | | | _-‾ | // | | _-‾ | | | _-‾ | - // | 0-------1-‾ | | | _-‾ | + // | 0───────1-‾ | | | _-‾ | // *-------------*-------------* 0═══════════════1‾--------------* // @@ -1962,21 +1955,21 @@ TEST_F(StaircaserTest, structureMoreThanOneCell) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + ASSERT_EQ(resultMesh.groups.size(), 1); assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } -TEST_F(StaircaserTest, verifyOrderInSelectiveStructurer) +TEST_F(StaircaserTest, verifyOrderInSelectiveStaircaser) { // *-------------*-------------* (4->3)══════════(3->2)-------------* - // | 4-------3-_ | | | ‾-_ | + // | 4───────3-_ | | | ‾-_ | // | | ‾-_ | | | ‾-_ | // | | ‾--_2 | -> | | ‾(2->4) | // | | _-‾ | | | _- | // | | _-‾ | | | _-‾ | - // | 0-------1-‾ | | | _-‾ | + // | 0───────1-‾ | | | _-‾ | // *-------------*-------------* 0═══════════════1‾--------------* // @@ -2024,21 +2017,21 @@ TEST_F(StaircaserTest, verifyOrderInSelectiveStructurer) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + ASSERT_EQ(resultMesh.groups.size(), 1); assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } -TEST_F(StaircaserTest, structureSpecificTriangles) +TEST_F(StaircaserTest, staircaseSpecificTriangles) { // *-------------*-------------* (4->3)══════════(3->2)-------------* // | 4-------3-_ | ║///////////////║ ‾-_ | - // | | /║ ‾-_ | ║///////////////║ ‾-_ | - // | | / ║ ‾--_2 | -> ║///////////////║ ‾(2->4) | - // | | / ║ _-‾ | ║///////////////║ _- | - // | |/ ║ _-‾ | ║///////////////║ _-‾ | - // | 0-------1-‾ | ║///////////////║ _-‾ | + // | │ /║ ‾-_ | ║///////////////║ ‾-_ | + // | │ / ║ ‾--_2 | -> ║///////////////║ ‾(2->4) | + // | │ / ║ _-‾ | ║///////////////║ _- | + // | │/ ║ _-‾ | ║///////////////║ _-‾ | + // | 0───────1-‾ | ║///////////////║ _-‾ | // *-------------*-------------* 0═══════════════1‾--------------* // @@ -2087,28 +2080,28 @@ TEST_F(StaircaserTest, structureSpecificTriangles) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + ASSERT_EQ(resultMesh.groups.size(), 1); assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } -TEST_F(StaircaserTest, selectiveStructurerFillingGapsInFrontier_Split) +TEST_F(StaircaserTest, selectiveStaircaserFillingGapsInFrontier_Split) { - // 4══════════_-=2 5═════════════3 - // ⫽| _-‾‾ /║ ⫽\\\\\\\\\\\\\⫽║ - // ⫽ | __-‾ / ║ ⫽\\\\\\\\\\\\\⫽/║ - // ⫽ _┼-‾ / |║ ⫽\\\\\\\\\\\\\⫽//║ - // 0=‾-┼---------* |║ 0═════════════1////║ - // /║⟍ | /| | ║ /║ | ⫽/║///║ - // / ║ ⟍*-------╱-┼-┼-3 / ║ *------╱╱-║///4 - // / || / ⟍ ╱ || ⫽ / || / ╱ ╱ ║//⫽ - // *--┼┼--------⟍* ||⫽ *--┼┼------⌿-* ║/⫽ - // | | |/ | ⟍ ║⫽ | | |/ ╱ | ║⫽ - // | | *---------┼-_=1 | | *--╱------┼-_=2 - // || / _-‾|‾ / || / ╱ _-‾|‾ / - // ||/ __-‾ | / ||/╱ __-‾ | / - // ║/ __-‾ |/ ║⫽ __-‾ |/ + // 4══════════_-=2 5═══════════════3 + // ⫽| _-‾‾ /║ ⫽\\\\\\\\\\\\\⫽/║ + // ⫽ | __-‾ /⎹║ ⫽\\\\\\\\\\\\\⫽//║ + // ⫽ _┼-‾ / |║ ⫽\\\\\\\\\\\\\⫽///║ + // 0=‾-┼---------* |⎹⎸ 0═════════════1////⎹⎸ + // /║⟍ | /| ⎹ ║ /║ | ⫽║/////║ + // /⎹| ⟍*-------╱-┼-┼-3 /⎹| *------╱╱-⎹⎸////4 + // / || / ⟍ ╱ |⎹ ⫽ / || / ╱ ╱ ║///⫽ + // *--┼┼--------⟍* |⎸⫽ *--┼┼------/--* ║//⫽ + // | ⎹ |/ | ⟍ ║⫽ ⎹ ⎹ |/ ╱ | ║⫽ + // | ⎸ *---------┼-_=1 ⎹ ⎸ *--╱------┼-_-=2 + // || / _-‾|‾ / || / ╱ _-‾|‾ / + // |⎸/ __-‾ | / |⎸/╱ __-‾ | / + // ║/ __-‾ |/ ║⫽ __-‾ ⎸/ // 5=‾-----------* 6=‾-----------* float lowerCoordinateValue = -5.0; @@ -2166,29 +2159,29 @@ TEST_F(StaircaserTest, selectiveStructurerFillingGapsInFrontier_Split) auto resultMesh = staircaser.getSelectiveMesh(cellSet, Staircaser::GapsFillingType::Split); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + ASSERT_EQ(resultMesh.groups.size(), 1); assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } -TEST_F(StaircaserTest, selectiveStructurerFillingGapsInFrontier_Insert) +TEST_F(StaircaserTest, selectiveStaircaserFillingGapsInFrontier_Insert) { - // 4══════════_-=2 5═════════════3 - // ⫽| _-‾‾ /║ ⫽\\\\\\\\\\\\\⫽║ - // ⫽ | __-‾ / ║ ⫽\\\\\\\\\\\\\⫽/║ - // ⫽ _┼-‾ / |║ ⫽\\\\\\\\\\\\\⫽//║ - // 0=‾-┼---------* |║ 0═════════════1////║ - // /║⟍ | /| | ║ /║⟍ | /║///║ - // / ║ ⟍*-------╱-┼-┼-3 / ║ ⟍*-------╱-║///4 - // / || / ⟍ ╱ || ⫽ / || / ⟍ ╱ ║//⫽ - // *--┼┼--------⟍* ||⫽ *--┼┼--------⟍* ║/⫽ - // | | |/ | ⟍ ║⫽ | | |/ | ⟍ ║⫽ - // | | *---------┼-_=1 | | *---------┼-_=2 - // || / _-‾|‾ / || / _-‾|‾ / - // ||/ __-‾ | / ||/ __-‾ | / - // ║/ __-‾ |/ ║/ __-‾ |/ - // 5=‾-----------* 6=‾-----------* + // 4══════════_-=2 5══════════════3 + // ⫽| _-‾‾ /║ ⫽\\\\\\\\\\\\\⫽⎹⎸ + // ⫽ | __-‾ /⎹║ ⫽\\\\\\\\\\\\\⫽/⎹⎸ + // ⫽ _┼-‾ / |║ ⫽\\\\\\\\\\\\\⫽//⎹⎸ + // 0=‾-┼---------* |⎹⎸ 0═══════════════1///║ + // /║⟍ |z /| ⎹ ║ /║⟍ | /║///⎹⎸ + // /⎹| ⟍*-------╱-┼-┼-3 / ║ ⟍*--------╱-║///4 + // y/ || / ⟍ ╱ |⎹ ⫽ / || / ⟍ ╱ ⎹⎸//⫽ + // *--┼┼--------⟍* |⎸⫽ *--┼┼--------⟍* ⎹⎸/⫽ + // | | |/ | ⟍ ║⫽ | | |/ | ⟍ ⎹⎸⫽ + // |⎹ *---------┼-_=1 | | *---------┼-_==2 + // || / _-‾|‾ / || / _-‾|‾ / + // |⎸/ __-‾ | / ||/ __-‾ | / + // ║/ __-‾ |/ ║/ __-‾ | / + // 5=‾-----------* x 6=‾-----------* float lowerCoordinateValue = -5.0; float upperCoordinateValue = 5.0; @@ -2245,21 +2238,21 @@ TEST_F(StaircaserTest, selectiveStructurerFillingGapsInFrontier_Insert) auto resultMesh = staircaser.getSelectiveMesh(cellSet, Staircaser::GapsFillingType::Insert); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + ASSERT_EQ(resultMesh.groups.size(), 1); assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } -TEST_F(StaircaserTest, selectiveStructurer_SplitLinesWithNeighborTriangle) +TEST_F(StaircaserTest, selectiveStaircaser_SplitLinesWithNeighborTriangle) { // *----0════════2═════════════5 *---(0->3)==(2->5)════════(5->6) - // | ‾-_ ║|\ ║ | ‾-_ ║ \ ║ - // | ‾-_ ║ | \ ║ | ‾-_ ║ \ ║ - // | - ║ | \ ║ | - ║ \ ║ - // | ‾1 | \ ║ | (1->4) \ ║ - // | |‾- | \ ║ | ║‾‾--__ \ ║ - // | | ‾-_| \ ║ | ║ ‾‾--_\ ║ + // | ‾-_ ║\\ ║ | ‾-_ ║ \ ║ + // | ‾-_ ║ \ \ ║ | ‾-_ ║ \ ║ + // | - ║ \ \ ║ | - ║ \ ║ + // | ‾1 \ \ ║ | (1->4) \ ║ + // | |‾- \ \ ║ | ║‾‾--__ \ ║ + // | | ‾-_\ \ ║ | ║ ‾‾--_\ ║ // *-------------*-----3═══════4 -> *-----------(3->0)════════(4->1) // | | \ | | | ║ // | | \ | | | ║ @@ -2322,7 +2315,7 @@ TEST_F(StaircaserTest, selectiveStructurer_SplitLinesWithNeighborTriangle) auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); + ASSERT_EQ(resultMesh.groups.size(), 1); assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); From 6a5061989b470c85b37f189dd010c689d5ff4008 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Thu, 27 Nov 2025 17:13:42 +0100 Subject: [PATCH 11/35] WIP | DRAFT | tests for selective staircasing with overlapping --- test/core/StaircaserTest.cpp | 225 +++++++++++++++++++++++++++++++++-- 1 file changed, 218 insertions(+), 7 deletions(-) diff --git a/test/core/StaircaserTest.cpp b/test/core/StaircaserTest.cpp index 8646403..ca23f2a 100644 --- a/test/core/StaircaserTest.cpp +++ b/test/core/StaircaserTest.cpp @@ -2246,7 +2246,7 @@ TEST_F(StaircaserTest, selectiveStaircaserFillingGapsInFrontier_Insert) TEST_F(StaircaserTest, selectiveStaircaser_SplitLinesWithNeighborTriangle) { - // *----0════════2═════════════5 *---(0->3)==(2->5)════════(5->6) + // *----0════════2═════════════5 *---(0->3)══(2->5)════════(5->6) // | ‾-_ ║\\ ║ | ‾-_ ║ \ ║ // | ‾-_ ║ \ \ ║ | ‾-_ ║ \ ║ // | - ║ \ \ ║ | - ║ \ ║ @@ -2254,12 +2254,12 @@ TEST_F(StaircaserTest, selectiveStaircaser_SplitLinesWithNeighborTriangle) // | |‾- \ \ ║ | ║‾‾--__ \ ║ // | | ‾-_\ \ ║ | ║ ‾‾--_\ ║ // *-------------*-----3═══════4 -> *-----------(3->0)════════(4->1) - // | | \ | | | ║ - // | | \ | | | ║ - // | | \ | | | ║ - // | | \ | | | ║ - // | | \ | | | ║ - // | | \ | | | ║ + // | | \ ║ | | ║ + // | | \ ║ | | ║ + // | | \ ║ | | ║ + // | | \ ║ | | ║ + // | | \ ║ | | ║ + // | | \ ║ | | ║ // *-------------*-------------6 *-------------*-----------(6->2) float lowerCoordinateValue = -5.0; @@ -2321,4 +2321,215 @@ TEST_F(StaircaserTest, selectiveStaircaser_SplitLinesWithNeighborTriangle) assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } +TEST_F(StaircaserTest, selectiveStaircaser_staircase_overlapping_1) +{ + + // z z + // *---------------*---------------*---------------* *---------------*---------------*---------------* + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | _-10-__ | | | | _-8--__ | | + // | |_-‾ ‾‾--_| | | |_-‾ ‾‾--_| | + // 9═══════8═══════5═══════════════4---------------* 7═══════6═══════3═══════════════2---------------* + // |\ │ /║ ╱║‾--__ | |\ │ /║ -> ║‾--__ | + // | \ │ / ║ ╱ ║ _=11 | | \ │ / ║ ║ ‾9 | + // | \ │ / ║ ╱ ║ _- | | \ │ / ║ ║ / | + // | \ │ / ║ 1 ───╱────==3-‾ | -> | \ │ / ║ ^ | ║ / | + // | \ │ / ║ /⟍ ╱ __-‾ ⎸ ⎸ ⎸ \ ⎸ / ║ | v ║ / ⎸ + // | \ │ / ║ / ╱_⟍-‾ ⎸ ⎸ ⎸ \ ⎸ / ║ ║ / ⎸ + // | \│/ ║/╱-‾ ⟍ ⎸ ⎸ ⎸ \⎸/ ║ <- ║ / ⎸ + // *-------7═══════0═════════2-----*---------------* *-------5═══════0═══════════════1---------------* + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \║/ | | | \║ / | | + // *---------------6---------------*---------------* x *---------------4---------------*---------------* x + // + float lowerCoordinateValue = -5.0; + float upperCoordinateValue = 10.0; + int numberOfCells = 3; + float step = 5.0; + assert((upperCoordinateValue - lowerCoordinateValue) / numberOfCells == step); + + std::set cellSet; + cellSet.insert(Cell({ 1, 0, 1 })); + + Mesh mesh; + mesh.grid = GridTools::buildCartesianGrid(lowerCoordinateValue, upperCoordinateValue, numberOfCells + 1); + mesh.coordinates = { + Relative({ 1.00, 0.00, 1.00 }), // 0 + Relative({ 1.20, 0.00, 1.40 }), // 1 + Relative({ 1.66, 0.00, 1.00 }), // 2 + Relative({ 2.00, 0.00, 1.40 }), // 3 + Relative({ 2.00, 0.00, 2.00 }), // 4 + Relative({ 1.00, 0.00, 2.00 }), // 5 + Relative({ 1.00, 0.00, 0.00 }), // 6 + Relative({ 0.50, 0.00, 1.00 }), // 7 + Relative({ 0.50, 0.00, 2.00 }), // 8 + Relative({ 0.00, 0.00, 2.00 }), // 9 + Relative({ 1.40, 0.00, 2.25 }), // 10 + Relative({ 2.40, 0.00, 1.75 }), // 11 + }; + + mesh.groups.resize(1); + mesh.groups[0].elements = { + Element({0, 1, 2}, Element::Type::Surface), // 0, Clock-wise + Element({1, 0, 3}, Element::Type::Surface), // 1, Counter-Clock-wise + Element({3, 0, 4}, Element::Type::Surface), // 2, Clock-wise + Element({4, 0, 5}, Element::Type::Surface), // 3, Clock-wise + Element({0, 2, 6}, Element::Type::Surface), // 4, Clock-wise + Element({0, 6, 7}, Element::Type::Surface), // 5, Clock-wise + Element({0, 7, 5}, Element::Type::Surface), // 6, Clock-wise + Element({5, 7, 8}, Element::Type::Surface), // 7, Clock-wise + Element({8, 7, 9}, Element::Type::Surface), // 8, Clock-wise + Element({4, 5, 10}, Element::Type::Surface), // 9, Clock-wise + Element({3, 4, 11}, Element::Type::Surface),// 10, Clock-wise + }; + + Relatives expectedRelatives = { + Relative({ 1.00, 0.00, 1.00 }), // (0|1->0) + Relative({ 2.00, 0.00, 1.00 }), // (2|3->1) + Relative({ 2.00, 0.00, 2.00 }), // (4->2) + Relative({ 1.00, 0.00, 2.00 }), // (5->3) + Relative({ 1.00, 0.00, 0.00 }), // (6->4) + Relative({ 0.50, 0.00, 1.00 }), // (7->5) + Relative({ 0.50, 0.00, 2.00 }), // (8->6) + Relative({ 0.00, 0.00, 2.00 }), // (9->7) + Relative({ 1.40, 0.00, 2.25 }), // (10->8) + Relative({ 2.40, 0.00, 1.75 }), // (11->9) + }; + + Elements expectedElements = { + Element({0}, Element::Type::Node), // 0, 1 -> 0 + Element({0, 1}, Element::Type::Line), // 0, 2 -> 1 + Element({1, 0}, Element::Type::Line), // 0, 2 -> 2 + Element({1, 2}, Element::Type::Line), // 1, 2 -> 3 + Element({2, 1}, Element::Type::Line), // 1, 2 -> 4 + Element({2, 1, 0, 3}, Element::Type::Surface), // 3 -> 5 | Clock-wise + Element({0, 4, 5}, Element::Type::Surface), // 5 -> 6 | Clockwise + Element({0, 5, 3}, Element::Type::Surface), // 6 -> 7 | Clockwise + Element({3, 5, 6}, Element::Type::Surface), // 7 -> 8 | Clockwise + Element({6, 5, 7}, Element::Type::Surface), // 8 -> 9 | Clockwise + Element({0, 1, 4}, Element::Type::Surface), // 4 -> 10 | Clockwise + Element({2, 3, 8}, Element::Type::Surface), // 9 -> 11 | Clockwise + Element({1, 2, 9}, Element::Type::Surface), // 10 -> 12 | Clockwise + }; + + auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); + + ASSERT_EQ(resultMesh.groups.size(), 1); + + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); +} + +TEST_F(StaircaserTest, selectiveStaircaser_staircase_overlapping_2) +{ + + // z z + // *---------------*---------------*---------------* *---------------*---------------*---------------* + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | _-8--__ | | | | _-6--__ | | + // | |_-‾ ‾‾--_| | | |_-‾ ‾‾--_| | + // *---------------5═══════════════4---------------* *---------------1═══════════════3---------------* + // | /║ ╱║‾--__ | | /║ -> ║‾--__ | + // | / ║ ╱ ║ __=9 | | / ║ ║ ‾7 | + // | / ║ 1─────╱───=3-‾‾ | | / ║ ║ | + // | / ║ / \ ╱ _-‾ | | -> | / ║ ^ | ║ | + // | / ║ / ╱\ _-‾ | | | / ║ | v ║ | + // | / ║ / ╱ _-\ | | | / ║ ║ | + // | / ║/╱_-‾ \ | | | / ║ <- ║ | + // *-------7═══════0═════════2-----*---------------* *-------5═══════0═══════════════2---------------* + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \║/ | | | \║ / | | + // *---------------6---------------*---------------* x *---------------4---------------*---------------* x + // + float lowerCoordinateValue = -5.0; + float upperCoordinateValue = 10.0; + int numberOfCells = 3; + float step = 5.0; + assert((upperCoordinateValue - lowerCoordinateValue) / numberOfCells == step); + + std::set cellSet; + cellSet.insert(Cell({ 1, 0, 1 })); + + Mesh mesh; + mesh.grid = GridTools::buildCartesianGrid(lowerCoordinateValue, upperCoordinateValue, numberOfCells + 1); + mesh.coordinates = { + Relative({ 1.00, 0.00, 1.00 }), // 0 + Relative({ 1.40, 0.00, 1.60 }), // 1 + Relative({ 1.66, 0.00, 1.00 }), // 2 + Relative({ 2.00, 0.00, 1.60 }), // 3 + Relative({ 2.00, 0.00, 2.00 }), // 4 + Relative({ 1.00, 0.00, 2.00 }), // 5 + Relative({ 1.00, 0.00, 0.00 }), // 6 + Relative({ 0.50, 0.00, 1.00 }), // 7 + Relative({ 1.40, 0.00, 2.25 }), // 8 + Relative({ 2.40, 0.00, 1.75 }), // 9 + }; + + mesh.groups.resize(1); + mesh.groups[0].elements = { + Element({0, 1, 2}, Element::Type::Surface), // 0, Clock-wise + Element({1, 0, 3}, Element::Type::Surface), // 1, Counter-Clock-wise + Element({3, 0, 4}, Element::Type::Surface), // 2, Clock-wise + Element({4, 0, 5}, Element::Type::Surface), // 3, Clock-wise + Element({0, 2, 6}, Element::Type::Surface), // 4, Clock-wise + Element({0, 6, 7}, Element::Type::Surface), // 5, Clock-wise + Element({0, 7, 5}, Element::Type::Surface), // 6, Clock-wise + Element({4, 5, 8}, Element::Type::Surface), // 7, Clock-wise + Element({3, 4, 9}, Element::Type::Surface), // 8, Clock-wise + }; + + Relatives expectedRelatives = { + Relative({ 1.00, 0.00, 1.00 }), // (0->0) + Relative({ 1.00, 0.00, 2.00 }), // (5|1->1) + Relative({ 2.00, 0.00, 1.00 }), // (2->2) + Relative({ 2.00, 0.00, 2.00 }), // (4->3) + Relative({ 1.00, 0.00, 0.00 }), // (6->4) + Relative({ 0.50, 0.00, 1.00 }), // (7->5) + Relative({ 1.40, 0.00, 2.25 }), // (8->6) + }; + + Elements expectedElements = { + Element({0, 1}, Element::Type::Line), // 0, -> 0 + Element({1, 0}, Element::Type::Line), // 0, -> 1 + Element({0, 2}, Element::Type::Line), // 0, -> 2 + Element({2, 0}, Element::Type::Line), // 0, -> 3 + Element({1, 0, 2, 3}, Element::Type::Surface), // 2 -> 4 | Counter - Clock-wise!!!! + Element({3, 2}, Element::Type::Line), // 5 -> 5 | Clockwise + Element({2, 3}, Element::Type::Line), // 5 -> 6 | Clockwise + Element({3}, Element::Type::Node), // 5 -> 7 | Clockwise + Element({3, 2, 0, 1}, Element::Type::Surface), // 2 -> 8 | Clockwise + Element({0, 4, 5}, Element::Type::Surface), // 6 -> 9 | Clockwise + Element({0, 5, 1}, Element::Type::Surface), // 4 -> 10 | Clockwise + Element({0, 2, 4}, Element::Type::Surface), // 7 -> 11 | Clockwise + Element({3, 1, 6}, Element::Type::Surface), // 8 -> 12 | Clockwise + //Element({3, 3, 7}, Element::Type::Surface), // 8 -> 13 | Degenerate + + }; + + auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet); + + ASSERT_EQ(resultMesh.groups.size(), 1); + + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); +} + } \ No newline at end of file From b7e540940fc44ff4be5fda4e7c8fff0899511a98 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Mon, 15 Dec 2025 15:52:46 +0100 Subject: [PATCH 12/35] Core | Make delaunator without cgal --- README.md | 2 + src/cgal/Delaunator.h | 16 +-- src/core/CMakeLists.txt | 1 + src/core/Delaunator.cpp | 139 ++++++++++++++++++++++ src/core/Delaunator.h | 39 ++++++ src/utils/Geometry.h | 2 +- test/CMakeLists.txt | 1 + test/core/DelaunatorTest.cpp | 223 +++++++++++++++++++++++++++++++++++ vcpkg.json | 1 + 9 files changed, 415 insertions(+), 9 deletions(-) create mode 100644 src/core/Delaunator.cpp create mode 100644 src/core/Delaunator.h create mode 100644 test/core/DelaunatorTest.cpp diff --git a/README.md b/README.md index b777cec..0a4bc9d 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ This code and its copyright is property of to the University of Granada (UGR), C testData/cervezas_alhambra logo has been downloaded from https://cults3d.com/es/modelo-3d/arte/celosia-alhambra-logo-cervezas-alhambra where is available with license CC BY-NC-SA. +Third party library CDT has been used as an external library. Its source code can be found in https://github.com/artem-ogre/CDT, licensed under the MPL-2.0 license. + ## Funding - Spanish Ministry of Science and Innovation (MICIN/AEI) (Grant Number: PID2022-137495OB-C31) diff --git a/src/cgal/Delaunator.h b/src/cgal/Delaunator.h index 7aac972..2e283b9 100644 --- a/src/cgal/Delaunator.h +++ b/src/cgal/Delaunator.h @@ -48,22 +48,22 @@ class Delaunator { typedef CGAL::Constrained_triangulation_face_base_2 Fb; typedef CGAL::Triangulation_data_structure_2 TDS; typedef CGAL::Exact_predicates_tag Itag; - typedef CGAL::Constrained_Delaunay_triangulation_2 CDT; - typedef CDT::Point Point; + typedef CGAL::Constrained_Delaunay_triangulation_2 Triangulation; + typedef Triangulation::Point Point; typedef CGAL::Polygon_2 Polygon_2; - typedef CDT::Face_handle Face_handle; + typedef Triangulation::Face_handle Face_handle; - typedef boost::bimap IndexPointToId; + typedef boost::bimap IndexPointToId; IndexPointToId buildPointsInIndex(const IdSet& inIds, const Polygons& constraint) const; - static CDT buildCDT( + static Triangulation buildCDT( const IndexPointToId& pointToId, const IdSet& inIds, const Polygons& constrainingPolygons); - std::vector convertFromCDT(const CDT& cdt, const IndexPointToId& pointToId) const; + std::vector convertFromCDT(const Triangulation& cdt, const IndexPointToId& pointToId) const; - static void mark_domains(CDT& cdt); - static void mark_domains(CDT& ct, Face_handle start, int index, std::list& border); + static void mark_domains(Triangulation& cdt); + static void mark_domains(Triangulation& ct, Face_handle start, int index, std::list& border); void checkIdsAreInRange( const IdSet& inIds, const std::vector& constrainingPolygons) const; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 343d9bc..d1e6c0e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(tessellator-core "SmootherTools.cpp" "Staircaser.cpp" "Splicer.cpp" + "Delaunator.cpp" ) target_link_libraries(tessellator-core tessellator-utils) \ No newline at end of file diff --git a/src/core/Delaunator.cpp b/src/core/Delaunator.cpp new file mode 100644 index 0000000..8772259 --- /dev/null +++ b/src/core/Delaunator.cpp @@ -0,0 +1,139 @@ +#include "Delaunator.h" +#include "utils\Geometry.h" + +namespace meshlib::core { + +Delaunator::Delaunator(const Coordinates& globalCoordinates) : globalCoordinates_(&globalCoordinates) {} + +Elements Delaunator::mesh(const Polygons& constrainingPolygons) const { + try { + IdSet targetVertices; + + for (auto& polygon : constrainingPolygons) { + targetVertices.insert(polygon.begin(), polygon.end()); + } + + checkConstraintsArePlanar(targetVertices); + + IndexPointToId pointsToIds; + + Triangulation cdt = buildCDT(pointsToIds, targetVertices, constrainingPolygons); + + cdt.eraseOuterTrianglesAndHoles(); + + return convertFromCDT(cdt, pointsToIds); + } + catch (std::out_of_range& e) { + throw std::runtime_error("Coordinate ids are out of range."); + } +} + +void Delaunator::checkConstraintsArePlanar(const IdSet & targetVertices) const { + Coordinates targetCoordinates; + targetCoordinates.reserve(targetVertices.size()); + + for (auto v : targetVertices) { + targetCoordinates.push_back(globalCoordinates_->at(v)); + } + + if (!utils::Geometry::areCoordinatesCoplanar(targetCoordinates.begin(), targetCoordinates.end())) { + throw std::runtime_error("Constraining polygons are not planar"); + } +} + +Delaunator::Triangulation Delaunator::buildCDT( + IndexPointToId& pointsToIds, + const IdSet& targetVertices, + const Polygons& constrainingPolygons) const { + Triangulation cdt; + + auto widestAxes = findWidestAxes(targetVertices); + + std::vector newVertices; + newVertices.reserve(targetVertices.size()); + + for (CoordinateId v : targetVertices) { + const auto coordinate = globalCoordinates_->at(v); + + Point point({ coordinate[widestAxes.first], coordinate[widestAxes.second] }); + newVertices.push_back(point); + + PointId pointId(newVertices.size() - 1); + pointsToIds.insert(IndexPointToId::value_type(pointId, v)); + } + + cdt.insertVertices(newVertices); + + std::vector edges; + edges.reserve(targetVertices.size()); + + for (const auto& polygon : constrainingPolygons) { + for (std::size_t i = 0; i < polygon.size(); ++i) { + std::set edgeSet({ + pointsToIds.right.at(polygon[i]), + pointsToIds.right.at(polygon[(i + 1) % polygon.size()]) + }); + + edges.emplace_back(*edgeSet.begin(), *edgeSet.rbegin()); + } + } + + cdt.insertEdges(edges); + + return cdt; +} + +std::pair Delaunator::findWidestAxes(const IdSet& targetVertexes) const { + auto start = targetVertexes.begin(); + Coordinate lowestValues = globalCoordinates_->at(*start); + Coordinate highestValues = lowestValues; + + auto vIt = std::next(start); + for (vIt; vIt != targetVertexes.end(); ++vIt) { + const Coordinate& coordinate = globalCoordinates_->at(*vIt); + + for (Axis axis = X; axis <= Z; ++axis) { + if (coordinate[axis] < lowestValues[axis]) { + lowestValues[axis] = coordinate[axis]; + } + if (coordinate[axis] > highestValues[axis]) { + highestValues[axis] = coordinate[axis]; + } + } + } + + auto distance = highestValues - lowestValues; + Axis shortestDistance = X; + for (Axis axis = Y; axis <= Z; ++axis) { + if (distance[axis] < distance[shortestDistance]) { + shortestDistance = axis; + } + } + std::set orderedAxes({ (shortestDistance + 1) % 3, (shortestDistance + 2) % 3 }); + Axis first = *orderedAxes.begin(); + Axis second = *std::next(orderedAxes.begin()); + + return std::make_pair(first, second); +} + +Elements Delaunator::convertFromCDT(const Triangulation& cdt, const IndexPointToId& pointToId) const { + auto newTriangles = cdt.triangles; + + Elements result; + result.reserve(newTriangles.size()); + + for (const auto& triangle : newTriangles) { + CoordinateIds vertices; + vertices.reserve(3); + + for (PointId id : triangle.vertices) { + vertices.push_back(pointToId.left.at(id)); + } + + result.emplace_back(vertices, Element::Type::Surface); + } + + return result; +} + +} \ No newline at end of file diff --git a/src/core/Delaunator.h b/src/core/Delaunator.h new file mode 100644 index 0000000..60d2dc2 --- /dev/null +++ b/src/core/Delaunator.h @@ -0,0 +1,39 @@ +#pragma once + +#include "types/Mesh.h" +#include "utils/Types.h" + +#include + +#include + +namespace meshlib { +namespace core { + + +class Delaunator { +public: + typedef std::vector Polygon; + typedef std::vector Polygons; + + Delaunator(const Coordinates& globalCoordinates); + std::vector mesh(const std::vector& constrainingPolygons = std::vector()) const; + + +private: + const Coordinates* globalCoordinates_ = nullptr; + + typedef CDT::Triangulation Triangulation; + typedef CDT::V2d Point; + typedef CDT::VertInd PointId; + + typedef boost::bimap IndexPointToId; + + void checkConstraintsArePlanar(const IdSet& targetVertices) const; + Triangulation buildCDT(IndexPointToId& pointToId, const IdSet& targetVertices, const Polygons& constrainingPolygons) const; + std::pair findWidestAxes(const IdSet& targetVertexes) const; + Elements convertFromCDT(const Triangulation& cdt, const IndexPointToId& pointToId) const; +}; + +} +} diff --git a/src/utils/Geometry.h b/src/utils/Geometry.h index 113593b..63b8896 100644 --- a/src/utils/Geometry.h +++ b/src/utils/Geometry.h @@ -83,7 +83,7 @@ class Geometry { - a2 * (b1 * c3 - b3 * c1) + a3 * (b1 * c2 - b2 * c1); - const bool isCoplanar{ det < COPLANARITY_TOLERANCE }; + const bool isCoplanar{ abs(det) < COPLANARITY_TOLERANCE }; if (!isCoplanar) { return false; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 82b55b1..3553654 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(tessellator_tests "app/launcherTest.cpp" "app/vtkIOTest.cpp" "core/CollapserTest.cpp" + "core/DelaunatorTest.cpp" "core/SlicerTest.cpp" "core/SnapperTest.cpp" "core/SmootherTest.cpp" diff --git a/test/core/DelaunatorTest.cpp b/test/core/DelaunatorTest.cpp new file mode 100644 index 0000000..d5edbdf --- /dev/null +++ b/test/core/DelaunatorTest.cpp @@ -0,0 +1,223 @@ +#include "gtest/gtest.h" + +#include "core/Delaunator.h" +#include "utils/Types.h" + +namespace meshlib::core { + +class DelaunatorTest : public ::testing::Test { +public: + static Coordinates buildCoordinates() + { + return Coordinates { + Coordinate({0.00, 0.00, 0.00}), + Coordinate({0.00, 1.00, 0.00}), + Coordinate({1.00, 1.00, 0.00}), + Coordinate({1.00, 0.00, 0.00}), + Coordinate({5.00, 5.00, 5.00}), + Coordinate({0.00, 1.00, 0.00}), + Coordinate({0.75, 0.25, 0.00}) + }; + } + + static Coordinates buildPathCoordinates() + { + return Coordinates{ + Coordinate({0.0, 0.0, 0.0}), + Coordinate({0.0, 1.0, 0.0}), + Coordinate({1.0, 1.0, 0.0}), + Coordinate({1.0, 0.0, 0.0}), + Coordinate({0.25, 0.75, 0.0}), + Coordinate({0.75, 0.75, 0.0}), + Coordinate({5.0, 5.0, 5.0}), + }; + } +}; + +TEST_F(DelaunatorTest, mesh_one_triangle_with_constraining_polygon) +{ + auto coords = buildCoordinates(); + Delaunator delaunator(coords); + + auto tris = delaunator.mesh({ {0, 1, 2} }); + + EXPECT_EQ(1, tris.size()); + for (auto const& tri : tris) { + EXPECT_EQ(Element::Type::Surface, tri.type); + EXPECT_EQ(3, tri.vertices.size()); + } + +} + +TEST_F(DelaunatorTest, mesh_bowtie) +{ + std::vector coords(5); + coords [0] = Coordinate{{0.0, 0.0 , 0.0 }}; + coords [1] = Coordinate{{0.25, 0.25, 0.0 }}; + coords [2] = Coordinate{{0.5, 0.0 , 0.0 }}; + coords [3] = Coordinate{{0.75, 0.25, 0.0 }}; + coords [4] = Coordinate{{1.0, 0.0 , 0.0 }}; + + + Delaunator delaunator(coords); + IdSet cIds = { 0,1,2,3,4,2 }; + Delaunator::Polygon boundary = { 0,1,2,3,4,2 }; + auto tris = delaunator.mesh({ boundary }); + + EXPECT_EQ(2, tris.size()); + for (auto const& tri : tris) { + EXPECT_EQ(Element::Type::Surface, tri.type); + EXPECT_EQ(3, tri.vertices.size()); + } +} +TEST_F(DelaunatorTest, mesh_bowtie_2) +{ + std::vector coords(6); + coords [0] = Coordinate{{0.0, 0.0, 0.0 }}; + coords [1] = Coordinate{{1.0, 1.0, 0.0 }}; + coords [2] = Coordinate{{0.0, 1.0, 0.0 }}; + coords [3] = Coordinate{{1.0, 0.0, 0.0 }}; + coords [4] = Coordinate{{0.0, 0.25, 0.0 }}; + coords [5] = Coordinate{{0.2, 0.2, 0.0 }}; + + + Delaunator delaunator(coords); + Delaunator::Polygon boundary = { 0,5,4,1,2,4 }; + auto tris = delaunator.mesh({ boundary }); + + EXPECT_EQ(2, tris.size()); + for (auto const& tri : tris) { + EXPECT_EQ(Element::Type::Surface, tri.type); + EXPECT_EQ(3, tri.vertices.size()); + } +} + +TEST_F(DelaunatorTest, mesh_tri_with_hole) +{ + std::vector coords(6); + coords[0] = Coordinate{ {0.0, 0.0, 0.0 } }; + coords[1] = Coordinate{ {0.3, 0.0, 0.0 } }; + coords[2] = Coordinate{ {0.15, 0.3, 0.0 } }; + coords[3] = Coordinate{ {0.1, 0.1, 0.0 } }; + coords[4] = Coordinate{ {0.2, 0.1, 0.0 } }; + coords[5] = Coordinate{ {0.15, 0.2, 0.0 } }; + + + Delaunator delaunator(coords); + Delaunator::Polygon outer_boundary = { 0,1,2}; + Delaunator::Polygon inner_boundary = { 3,4,5 }; + auto tris = delaunator.mesh({ outer_boundary, inner_boundary }); + + EXPECT_EQ(6, tris.size()); + for (auto const& tri : tris) { + EXPECT_EQ(Element::Type::Surface, tri.type); + EXPECT_EQ(3, tri.vertices.size()); + } +} + +TEST_F(DelaunatorTest, mesh_one_triangle_other_call) +{ + auto coords = buildCoordinates(); + Delaunator delaunator(coords); + IdSet cIds = { 0,1,2 }; + Delaunator::Polygon boundary = { 0,1,2 }; + auto tris = delaunator.mesh({boundary}); + + EXPECT_EQ(1, tris.size()); + for (auto const& tri : tris) { + EXPECT_EQ(Element::Type::Surface, tri.type); + EXPECT_EQ(3, tri.vertices.size()); + } +} + +TEST_F(DelaunatorTest, mesh_with_invalid_constraining_polygon) +{ + std::vector coords(5); + coords[0] = Coordinate{ {0.25, 0.00, 0.00} }; + coords[1] = Coordinate{ {0.50, 0.00, 0.00} }; + coords[2] = Coordinate{ {1.00, 0.00, 0.50} }; + coords[3] = Coordinate{ {0.25, 0.00, 1.00} }; + coords[4] = Coordinate{ {1.00, 0.00, 1.00} }; + + Delaunator delaunator(coords); + { + std::vector constrainingPolygon = { 0,1,2,3,4 }; + EXPECT_ANY_THROW(auto tris = delaunator.mesh({ constrainingPolygon })); + } + { + std::vector constrainingPolygon = { 0,1,2,4,3 }; + auto tris = delaunator.mesh({ constrainingPolygon }); + for (const auto& tri : tris) { + for (const auto& vertex : tri.vertices) { + EXPECT_TRUE(find( + constrainingPolygon.begin(), + constrainingPolygon.end(), + vertex) != constrainingPolygon.end()); + } + } + } +} +TEST_F(DelaunatorTest, mesh_two_triangles) +{ + auto coords = buildCoordinates(); + Delaunator delaunator(coords); + + auto tris = delaunator.mesh({ {0, 1, 2, 3} }); + + EXPECT_EQ(2, tris.size()); + for (auto const& tri : tris) { + EXPECT_EQ(Element::Type::Surface, tri.type); + EXPECT_EQ(3, tri.vertices.size()); + } +} + +TEST_F(DelaunatorTest, mesh_example_path) +{ + auto coords = buildPathCoordinates(); + Delaunator delaunator(coords); + + auto tris = delaunator.mesh({ { 0, 1, 4, 5, 2, 3 } }); + + EXPECT_EQ(4, tris.size()); + for (auto const& tri : tris) { + EXPECT_EQ(Element::Type::Surface, tri.type); + EXPECT_EQ(3, tri.vertices.size()); + } +} + +TEST_F(DelaunatorTest, mesh_polygons_cw_or_ccw) { + Coordinates c{ + Coordinate({0.0, 0.0, 0.0}), + Coordinate({0.0, 1.0, 0.0}), + Coordinate({1.0, 1.0, 0.0}), + Coordinate({0.1, 0.2, 0.0}), + Coordinate({0.1, 0.9, 0.0}), + Coordinate({0.8, 0.9, 0.0}) + }; + + Delaunator delaunator(c); + Delaunator::Polygon p1 = { 0, 1, 2 }; + + auto trisCW = delaunator.mesh({ p1, { 3, 4, 5 } }); + auto trisCCW = delaunator.mesh({ p1, { 5, 4, 3 } }); + + EXPECT_EQ(trisCW.size(), trisCCW.size()); +} + +TEST_F(DelaunatorTest, throw_when_not_aligned) +{ + auto coords = buildCoordinates(); + Delaunator delaunator(coords); + + EXPECT_THROW(delaunator.mesh({ { 0, 1, 2, 4 } }), std::runtime_error); +} + +TEST_F(DelaunatorTest, throw_when_coordinateId_is_out_of_range) +{ + auto coords = buildCoordinates(); + Delaunator delaunator(coords); + + EXPECT_ANY_THROW(delaunator.mesh({{ 0, 1, 2, 350 }})); +} + +} \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index d4ed077..69f3e7b 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -4,6 +4,7 @@ "boost-program-options", { "name": "vtk", "default-features": false, "platform": "windows"}, "nlohmann-json", + "cdt", "gtest" ], "features": { From 85baff3b597b85dc23a996bfa457f3acc2ada9fb Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Mon, 15 Dec 2025 18:23:24 +0100 Subject: [PATCH 13/35] WIP | DRAFT | Use delaunator in smoother --- src/core/Smoother.cpp | 2 +- src/core/SmootherTools.cpp | 16 +++------------- src/core/SmootherTools.h | 6 +++++- test/core/SmootherToolsTest.cpp | 34 ++++++++++++++++----------------- 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/core/Smoother.cpp b/src/core/Smoother.cpp index 49b2044..93b271d 100644 --- a/src/core/Smoother.cpp +++ b/src/core/Smoother.cpp @@ -21,7 +21,7 @@ using namespace meshTools; Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : - sT_(SmootherTools(mesh.grid)), + sT_(SmootherTools(mesh.grid, mesh.coordinates)), opts_(opts) { meshTools::checkNoCellsAreCrossed(mesh); diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index fd38246..99f8b49 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -16,8 +16,8 @@ namespace core { using namespace utils; -SmootherTools::SmootherTools(const Grid& grid) : - GridTools(grid) +SmootherTools::SmootherTools(const Grid& grid, const Coordinates & globalCoordinates) : + GridTools(grid), delaunator_(globalCoordinates) {} CoordinateId SmootherTools::getClosestEndOfPaths( @@ -330,17 +330,7 @@ void SmootherTools::remeshWithNoInteriorPoints( } auto cPolygons = g.getBoundaryGraph().findCycles(); - Elements remeshedEls; - for (auto& cPolygon : cPolygons) { - for (std::size_t i = 0; i < cPolygon.size() - 2; i++) { - remeshedEls.push_back( Element({ - cPolygon[0], - cPolygon[i + 1], - cPolygon[i + 2] - }, - Element::Type::Surface)); - } - } + Elements remeshedEls = delaunator_.mesh(cPolygons); for (auto & element: remeshedEls){ if (hasWrongOrientation(*patch[0], element, cs)) { diff --git a/src/core/SmootherTools.h b/src/core/SmootherTools.h index 973de4f..1083ba1 100644 --- a/src/core/SmootherTools.h +++ b/src/core/SmootherTools.h @@ -1,5 +1,7 @@ #pragma once +#include "Delaunator.h" + #include "utils/CoordGraph.h" #include "utils/GridTools.h" #include "utils/Tools.h" @@ -34,7 +36,7 @@ class SmootherTools : public utils::GridTools { IdSet edgeIds_; }; - SmootherTools(const Grid& grid); + SmootherTools(const Grid& grid, const Coordinates & globalCoordinates); void collapsePointsOnFeatureEdges( Coordinates& res, @@ -83,6 +85,8 @@ class SmootherTools : public utils::GridTools { const ElementsView& patch); private: + Delaunator delaunator_; + std::mutex writingCoordinates_; std::mutex writingElements_; diff --git a/test/core/SmootherToolsTest.cpp b/test/core/SmootherToolsTest.cpp index 0fb1b63..bb45869 100644 --- a/test/core/SmootherToolsTest.cpp +++ b/test/core/SmootherToolsTest.cpp @@ -471,7 +471,7 @@ TEST_F(SmootherToolsTest, remeshElementsAvoidingInteriorPoints) { Mesh m = buildTwoInnerPointsPatchMesh(); - SmootherTools sT(m.grid); + SmootherTools sT(m.grid, m.coordinates); Coordinates collapsed = m.coordinates; Elements elems = m.groups[0].elements; sT.remeshWithNoInteriorPoints(elems, m.coordinates, getView(elems)); @@ -498,7 +498,7 @@ TEST_F(SmootherToolsTest, remeshElementsAvoidingInteriorPoints_2) { Mesh m = buildTwoInnerPointsPatchMeshNotConnected(); - SmootherTools sT(m.grid); + SmootherTools sT(m.grid, m.coordinates); Coordinates collapsed = m.coordinates; Elements elems = m.groups[0].elements; sT.remeshWithNoInteriorPoints(elems, m.coordinates, getView(elems)); @@ -528,7 +528,7 @@ TEST_F(SmootherToolsTest, collapseEdge) Mesh mesh = buildCollapseEdgeMesh(); const Elements& es = mesh.groups[0].elements; - SmootherTools sT(mesh.grid); + SmootherTools sT(mesh.grid, mesh.coordinates); auto sIds = sT.buildSingularIds(es, mesh.coordinates, sSAngle); Coordinates collapsed = mesh.coordinates; @@ -551,7 +551,7 @@ TEST_F(SmootherToolsTest, collapsePointsOnCellFaces) Mesh mesh = buildTwoCellsPatchMesh(); const Elements& elems = mesh.groups[0].elements; - SmootherTools sT(mesh.grid); + SmootherTools sT(mesh.grid, mesh.coordinates); auto sIds = sT.buildSingularIds(elems, mesh.coordinates, sSAngle); Coordinates collapsed = mesh.coordinates; @@ -580,7 +580,7 @@ TEST_F(SmootherToolsTest, collapse_inner_point) Mesh mesh = buildInnerPointPatchMesh(); const Elements& elems = mesh.groups[0].elements; - SmootherTools sT(mesh.grid); + SmootherTools sT(mesh.grid, mesh.coordinates); Coordinates collapsed = mesh.coordinates; sT.collapseInteriorPointsToBound(collapsed, getView(elems)); @@ -605,7 +605,7 @@ TEST_F(SmootherToolsTest, collapse_inner_point_2) const Mesh mesh = buildInnerPointPatchMesh2(); const Elements& elems = mesh.groups[0].elements; - SmootherTools sT(mesh.grid); + SmootherTools sT(mesh.grid, mesh.coordinates); Coordinates collapsed = mesh.coordinates; sT.collapseInteriorPointsToBound(collapsed, getView(elems)); @@ -626,7 +626,7 @@ TEST_F(SmootherToolsTest, collapse_one_point_in_contour) Mesh mesh = buildOnePointInCellFacePatchMesh(); const Elements& elems = mesh.groups[0].elements; - SmootherTools sT(mesh.grid); + SmootherTools sT(mesh.grid, mesh.coordinates); Coordinates collapsed = sT.collapsePointsOnContour(elems, mesh.coordinates, alignmentAngle); @@ -648,7 +648,7 @@ TEST_F(SmootherToolsTest, collapse_two_points_in_contour) Mesh mesh = buildTwoPointsInCellFacePatchMesh(); const Elements& elems = mesh.groups[0].elements; - SmootherTools sT(mesh.grid); + SmootherTools sT(mesh.grid, mesh.coordinates); Coordinates collapsed = sT.collapsePointsOnContour(elems, mesh.coordinates, alignmentAngle); @@ -681,7 +681,7 @@ TEST_F(SmootherToolsTest, collapsePointsInContourWithInnerDent) Mesh mesh = buildCollapseEdgeMeshWithInnerDent(); const Elements& elements = mesh.groups[0].elements; - SmootherTools sT(mesh.grid); + SmootherTools sT(mesh.grid, mesh.coordinates); Coordinates collapsed = sT.collapsePointsOnContour(elements, mesh.coordinates, alignmentAngle); @@ -720,7 +720,7 @@ TEST_F(SmootherToolsTest, buildSingularIds) const Elements& es = m.groups[0].elements; - SmootherTools sT(m.grid); + SmootherTools sT(m.grid, m.coordinates); auto sIds = sT.buildSingularIds(es, cs, sSAngle); EXPECT_EQ(IdSet({ 0, 1, 2, 3, 4, 5}), sIds.contourIds()); @@ -735,7 +735,7 @@ TEST_F(SmootherToolsTest, collapsePointsOnFeatureEdges_singlePatch) Coordinates& cs = m.coordinates; Elements& es = m.groups[0].elements; - SmootherTools sT(m.grid); + SmootherTools sT(m.grid, m.coordinates); auto sIds = sT.buildSingularIds(es, cs, sSAngle); ElementsView p({&es[0], &es[1], &es[2]}); @@ -751,7 +751,7 @@ TEST_F(SmootherToolsTest, collapsePointsOnFeatureEdges_threePatches) Coordinates& cs = m.coordinates; Elements& es = m.groups[0].elements; - SmootherTools sT(m.grid); + SmootherTools sT(m.grid, m.coordinates); auto sIds = sT.buildSingularIds(es, cs, sSAngle); auto cells = sT.buildCellElemMap(es, cs); @@ -801,7 +801,7 @@ TEST_F(SmootherToolsTest, collapsePointsOnFeatureEdges_interior_in_face) {} // Corner ); - SmootherTools sT(m.grid); + SmootherTools sT(m.grid, m.coordinates); Coordinates collapsed = m.coordinates; for (auto const& cell : sT.buildCellElemMap(m.groups[0].elements, m.coordinates)) { for (auto const& p : Geometry::buildDisjointSmoothSets(cell.second, m.coordinates, sSAngle)) { @@ -854,7 +854,7 @@ TEST_F(SmootherToolsTest, collapsePointsOnFeatureEdges_interior_in_face_2) const Elements& es = m.groups[0].elements; Coordinates collapsed = m.coordinates; - SmootherTools(m.grid) + SmootherTools(m.grid, m.coordinates) .collapsePointsOnFeatureEdges(collapsed, {&es[0], &es[2], &es[5]}, sIds); m.coordinates = collapsed; @@ -879,7 +879,7 @@ TEST_F(SmootherToolsTest, collapsePointsOnFeatureEdges_feature_over_face) const Elements& es = m.groups[0].elements; { Coordinates collapsed = m.coordinates; - SmootherTools(m.grid) + SmootherTools(m.grid, m.coordinates) .collapsePointsOnFeatureEdges(collapsed, { &es[0], &es[1], &es[2], &es[3] }, sIds); EXPECT_EQ(6, countDifferentCoordinates(collapsed)); @@ -889,7 +889,7 @@ TEST_F(SmootherToolsTest, collapsePointsOnFeatureEdges_feature_over_face) } { Coordinates collapsed = m.coordinates; - SmootherTools(m.grid) + SmootherTools(m.grid, m.coordinates) .collapsePointsOnFeatureEdges(collapsed, { &es[4], &es[5], &es[6], &es[7] }, sIds); EXPECT_EQ(6, countDifferentCoordinates(collapsed)); @@ -923,7 +923,7 @@ TEST_F(SmootherToolsTest, collapsePointsOnFeatureEdges_feature_in_interior) const Elements& es = m.groups[0].elements; Coordinates collapsed = m.coordinates; - SmootherTools(m.grid) + SmootherTools(m.grid, m.coordinates) .collapsePointsOnFeatureEdges(collapsed, { &es[0], &es[1], &es[2], &es[3] }, sIds); EXPECT_EQ(6, countDifferentCoordinates(collapsed)); From c6b8afa0a6385a48d158e481a3926fb54120c220 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Tue, 16 Dec 2025 16:57:00 +0100 Subject: [PATCH 14/35] WIP | DRAFT | Use delaunator in smoother --- src/core/Smoother.cpp | 10 ++ src/core/SmootherTools.cpp | 75 ++++++++++- src/core/SmootherTools.h | 14 +- test/core/SmootherTest.cpp | 259 ++++++++++++++++++++++++++++++++++++- 4 files changed, 348 insertions(+), 10 deletions(-) diff --git a/src/core/Smoother.cpp b/src/core/Smoother.cpp index 93b271d..a8696aa 100644 --- a/src/core/Smoother.cpp +++ b/src/core/Smoother.cpp @@ -74,6 +74,16 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : patchs.begin(), patchs.end(), [&](auto& p) { sT_.collapseInteriorPointsToBound(res.coordinates, p); }); + /**/ + + std::for_each( +#ifdef TESSELLATOR_EXECUTION_POLICIES + std::execution::par, +#endif + patchs.begin(), patchs.end(), [&](auto& p) { + sT_.collapsePointsOnContourWithDelanautor(g.elements, res.coordinates, p, singularIds); + }); + /**/ } diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index 99f8b49..ee2b88f 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -201,7 +201,7 @@ void SmootherTools::collapsePointsOnCellEdges( IdSet movable = classifyIds(interior, [&](auto i) {return !protectedIds.count(i); }).first; IdSet validIds = mergeIds(cG.getExterior(), interiorValid); - + std::map toMove; for (auto const& i : movable) { if (isRelativeInCellCorner(coords[i])) { @@ -488,6 +488,79 @@ void SmootherTools::collapseInteriorPointsToBound( updateCoordinates(coords, toMove); } +void SmootherTools::collapsePointsOnContourWithDelanautor( + Elements& elems, + Coordinates& coords, + const ElementsView& patch, + const SingularIds& sIds) { + + if (patchIsPlanar(coords, patch)) { + CoordGraph g(patch); + + auto cPolygons = g.getBoundaryGraph().findCycles(); + std::vector newCPolygons; + std::map neighbours; + + newCPolygons.reserve(cPolygons.size()); + + bool equalCycles = true; + + for (const auto& cycle : cPolygons) { + CoordinateIds newCycle; + for (CoordinateId v : cycle) { + if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) != sIds.cornerIds().end()) { + newCycle.push_back(v); + } + else { + equalCycles = false; + } + } + + std::size_t nextNeighbourIndex = 0; + std::size_t previousNeighbourIndex = newCycle.size() - 1; + + for (CoordinateId v : cycle) { + if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) == sIds.cornerIds().end()) { + neighbours[v] = CoordinateIds{ newCycle[previousNeighbourIndex], newCycle[nextNeighbourIndex] }; + } + else { + neighbours[v] = CoordinateIds{ v, v }; + previousNeighbourIndex = nextNeighbourIndex; + nextNeighbourIndex = (nextNeighbourIndex + 1) % newCycle.size(); + } + } + + newCPolygons.push_back(newCycle); + } + + if (equalCycles) { + return; + } + + Elements remeshedEls = delaunator_.mesh(newCPolygons); + + for (auto& element : remeshedEls) { + if (hasWrongOrientation(*patch[0], element, coords)) { + reorientSingleElement(element); + } + } + + for (auto comp = remeshedEls.size(); comp < patch.size(); comp++) { + remeshedEls.push_back(Element({}, Element::Type::None)); + } + + if (remeshedEls.size() != patch.size()) { + throw std::logic_error("Not all elements have been remeshed"); + } + + const std::lock_guard lock(writingElements_); + for (auto comp = 0; comp < patch.size(); comp++) { + ElementId eId = patch[comp] - &elems.front(); + elems[eId] = remeshedEls[comp]; + } + } +} + void SmootherTools::collapseElementsInPatch( Elements& es, const ElementsView& p, diff --git a/src/core/SmootherTools.h b/src/core/SmootherTools.h index 1083ba1..b5dfdc1 100644 --- a/src/core/SmootherTools.h +++ b/src/core/SmootherTools.h @@ -74,6 +74,12 @@ class SmootherTools : public utils::GridTools { Coordinates& coords, const ElementsView& patch); + void collapsePointsOnContourWithDelanautor( + Elements& elems, + Coordinates& coords, + const ElementsView& patch, + const SingularIds&); + void remeshElementsToOneInteriorPoint( Elements& es, Coordinates& cs, @@ -84,6 +90,10 @@ class SmootherTools : public utils::GridTools { const Coordinates& cs, const ElementsView& patch); + static bool patchIsPlanar( + const Coordinates& cs, + const ElementsView& patch); + private: Delaunator delaunator_; @@ -132,10 +142,6 @@ class SmootherTools : public utils::GridTools { static void reorientSingleElement( Element& element); - static bool patchIsPlanar( - const Coordinates& cs, - const ElementsView& patch); - }; diff --git a/test/core/SmootherTest.cpp b/test/core/SmootherTest.cpp index 228ddb0..f2e2d70 100644 --- a/test/core/SmootherTest.cpp +++ b/test/core/SmootherTest.cpp @@ -1,4 +1,4 @@ -#include "gtest/gtest.h" +#include "gtest/gtest.h" #include "MeshFixtures.h" #include "Smoother.h" @@ -17,6 +17,141 @@ class SmootherTest : public ::testing::Test { protected: const double sSAngle = 30.0; const double alignmentAngle = 5.0; + + static void assertCoordinatesListEquals(const Coordinates& expectedCoordinates, const Coordinates& resultCoordinates) { + ASSERT_EQ(expectedCoordinates.size(), resultCoordinates.size()); + + for (CoordinateId c = 0; c < expectedCoordinates.size(); ++c) { + auto& expectedCoordinate = expectedCoordinates[c]; + auto& resultCoordinate = resultCoordinates[c]; + + for (Axis axis = X; axis <= Z; ++axis) { + EXPECT_EQ(expectedCoordinate[axis], resultCoordinate[axis]) + << "Current coordinate: #" << c << std::endl + << "Current Axis: #" << axis << std::endl; + } + } + } + + static void assertElementsListEquals(const Elements& expectedElements, const Elements& resultElements, GroupId g = 0) { + ASSERT_EQ(expectedElements.size(), resultElements.size()); + + for (ElementId e = 0; e < expectedElements.size(); ++e) { + const Element& expectedElement = expectedElements[e]; + const Element& resultElement = resultElements[e]; + + EXPECT_EQ(resultElement.vertices.size(), expectedElement.vertices.size()) + << "Current Group: #" << g << std::endl + << "Current Element: #" << e << std::endl; + EXPECT_EQ(resultElement.type, expectedElement.type) + << "Current Group: #" << g << std::endl + << "Current Element: #" << e << std::endl; + + for (std::size_t v = 0; v < resultElement.vertices.size(); ++v) { + EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]) + << "Current Group: #" << g << std::endl + << "Current Element: #" << e << std::endl + << "Current Vertex: #" << v << std::endl; + } + } + } + + static Mesh buildMeshWithInnerDent() + { + // z | z | z | z + // 10──────────────=9 | 9──────────────=16 | 16=──────────────17 | 17──────────────=10 + // │ _-‾⟋ │ | │ _-‾ │ | │ ⟍‾-_ │ | │ _-‾ │ + // │ _-‾ ⟋ │ | │ __-‾ │ | │ ⟍ ‾-_ │ | │ __-‾ │ + // │ _-‾ ⟋ │ | │ _-‾ │ | │ ⟍ ‾-_ │ | │ _-‾ │ + // │ _-‾ ⟋ │ | │ _-‾ │ | │ ⟍ ‾-_ │ | │ _-‾ │ + // 4,7═3,6═════════5,8 | 3,5,8══════════-13,15 | 15──────────13───14 | 14=-════════════4,7 + // │ _-‾-_ | │ ___--‾‾ │ | _-‾ \ │ | │ ___--‾‾ │ + // 2‾‾--__‾-_ | │ __--‾‾ │ | _-‾ ‾\│ | │ __--‾‾____----2 + // 0──────====1 x | 1=──────────────12 y | x 12──────────11 | y 11=======────────0 + // | | | + // | | | + // ------------------------------------------------------------------------------------------------------------------------- + // | | | + // 0────3─────1────5 x | | 9───────────────10 | + // │ . /│ / │ | | │ ⟍ │ | + // │ . / │ / │ | | │ ⟍ │ | 7___6____ + // │ . / │/ │ | | │ ⟍ │ | |‾‾‾-----==. + // │ ./ .│ │ | | │ ⟍ │ | 4 ----------8 + // │ /. . │ │ | | │ ⟍ │ | \‾‾‾---___ | + // │ / . . │ │ | | │ ⟍ │ | \3------==5 + // │/ .. │ │ | | │ ⟍ ⎸ | + // 11────13────12───15 | | x 16───────────────17 | + // y | | y | + // | | | + + Mesh res; + res.grid = utils::GridTools::buildCartesianGrid(0.0, 2.0, 3); + res.coordinates = { + Coordinate({ 0.00, 0.00, 0.00 }), // 0 + Coordinate({ 0.60, 0.00, 0.00 }), // 1 + Coordinate({ 0.00, 0.00, 0.10 }), // 2 + Coordinate({ 0.20, 0.00, 0.35 }), // 3 + Coordinate({ 0.00, 0.00, 0.40 }), // 4 + Coordinate({ 1.00, 0.00, 0.35 }), // 5 + Coordinate({ 0.20, 0.00, 0.45 }), // 6 + Coordinate({ 0.00, 0.00, 0.45 }), // 7 + Coordinate({ 1.00, 0.00, 0.40 }), // 8 + Coordinate({ 1.00, 0.00, 1.00 }), // 9 + Coordinate({ 0.00, 0.00, 1.00 }), // 10 + Coordinate({ 0.00, 1.00, 0.00 }), // 11 + Coordinate({ 0.60, 1.00, 0.00 }), // 12 + Coordinate({ 0.20, 1.00, 0.35 }), // 13 + Coordinate({ 0.00, 1.00, 0.40 }), // 14 + Coordinate({ 1.00, 1.00, 0.35 }), // 15 + Coordinate({ 1.00, 1.00, 1.00 }), // 16 + Coordinate({ 0.00, 1.00, 1.00 }), // 17 + }; + + res.groups.push_back(Group()); + res.groups[0].elements = { + Element({ 0, 1, 2 }, Element::Type::Surface), // 0 - + Element({ 1, 3, 2 }, Element::Type::Surface), // 1 - + Element({ 2, 3, 4 }, Element::Type::Surface), // 2 - + Element({ 3, 5, 4 }, Element::Type::Surface), // 3 - + Element({ 4, 5, 8 }, Element::Type::Surface), // 4 - + Element({ 4, 8, 7 }, Element::Type::Surface), // 5 - + Element({ 6, 7, 8 }, Element::Type::Surface), // 6 - + Element({ 6, 8, 9 }, Element::Type::Surface), // 7 - + Element({ 6, 9, 7 }, Element::Type::Surface), // 8 - + Element({ 7, 9, 10 }, Element::Type::Surface), // 9 - + + Element({ 11, 13, 12 }, Element::Type::Surface), // 10 - + Element({ 11, 14, 13 }, Element::Type::Surface), // 11 - + Element({ 13, 14, 16 }, Element::Type::Surface), // 12 - + Element({ 13, 16, 15 }, Element::Type::Surface), // 13 - + Element({ 14, 17, 16 }, Element::Type::Surface), // 14 - + + + Element({ 1, 12, 13 }, Element::Type::Surface), // 15 - + Element({ 1, 13, 3 }, Element::Type::Surface), // 16 - + Element({ 5, 15, 8 }, Element::Type::Surface), // 17 - + Element({ 8, 15, 16 }, Element::Type::Surface), // 18 - + Element({ 8, 16, 9 }, Element::Type::Surface), // 19 - + + Element({ 0, 2, 11 }, Element::Type::Surface), // 20 - + Element({ 2, 4, 11 }, Element::Type::Surface), // 21 - + Element({ 4, 14, 11 }, Element::Type::Surface), // 22 - + Element({ 4, 7, 14 }, Element::Type::Surface), // 23 - + Element({ 7, 10, 14 }, Element::Type::Surface), // 24 - + Element({ 10, 17, 14 }, Element::Type::Surface), // 25 - + + + Element({ 0, 11, 1 }, Element::Type::Surface), // 26 - + Element({ 1, 11, 12 }, Element::Type::Surface), // 27 - + Element({ 3, 13, 5 }, Element::Type::Surface), // 28 - + Element({ 5, 13, 15 }, Element::Type::Surface), // 29 - + + Element({ 9, 16, 17 }, Element::Type::Surface), // 30 - + Element({ 9, 17, 10 }, Element::Type::Surface), // 31 - + }; + + return res; + } }; TEST_F(SmootherTest, self_intersecting_after_smoothing) @@ -26,7 +161,7 @@ TEST_F(SmootherTest, self_intersecting_after_smoothing) // | {3,4} // | / \ // 0 -- 1 - + Mesh m; { m.grid = meshFixtures::buildUnitLengthGrid(1.0); @@ -46,7 +181,7 @@ TEST_F(SmootherTest, self_intersecting_after_smoothing) }; } - auto r{ Smoother{m}.getMesh() }; + auto r{ Smoother{m}.getMesh() }; EXPECT_EQ(2, countMeshElementsIf(r, isTriangle)); } @@ -62,9 +197,9 @@ TEST_F(SmootherTest, touching_by_single_point) { // Corner. // 4 - // /| + // /| // 3-0 - // /| + // /| // 1-2 m.grid = meshFixtures::buildUnitLengthGrid(1.0); m.coordinates = { @@ -87,6 +222,120 @@ TEST_F(SmootherTest, touching_by_single_point) EXPECT_EQ(1, countMeshElementsIf(r, isTriangle)); } +/// z z +/// 10──────────────=9 (10->5)──────────(9->4) +/// │ _-‾⟋ │ │\ ⟋ │ +/// │ _-‾ ⟋ │ │ \ ⟋ │ +/// │ _-‾ ⟋ │ │ \ ⟋ │ +/// │ _-‾ ⟋ │ -> │ \ ⟋ │ +/// 4 ,7═3,6═════════5,8 │ (3->2)──────(5->3) +/// │ _-‾-_ │ _/‾-_ +/// 2‾‾--__‾-_ │/ ‾-_ +/// 0──────====1 x 0─────────=1 x +/// +/// -------------------------------------------------------- +/// z z +/// 9──────────────=16 (9->4)─────────(16->10) +/// │ _-‾ │ │ ‾-_ │ +/// │ __-‾ │ │ ‾-__ │ +/// │ _-‾ │ │ ‾-_ │ +/// │ _-‾ │ -> │ ‾-_ │ +/// 3,5,8══════════-13,15 (3->2),(5->3)─────────(13->8),(15->9) +/// │ ___--‾‾ │ │ ___--‾‾ │ +/// │ __--‾‾ │ │ __--‾‾ │ +/// 1=──────────────12 y 1=───────────(12->7) y +/// +/// +/// ------------------------------------------------------------------ +/// +/// z z +/// 16=──────────────17 (16->10)─────────(17->11) +/// │ ⟍‾-_ │ │ ⟍ /│ +/// │ ⟍ ‾-_ │ │ ⟍ / │ +/// │ ⟍ ‾-_ │ │ ⟍ / │ +/// │ ⟍ ‾-_ │ -> │ ⟍ / │ +/// 15──────────13───14 (15->9)────(13->8) │ +/// _-‾ \ │ _-‾ \ │ +/// _-‾ ‾\│ _-‾ ‾\│ +/// x 12──────────11 x (12->7)────(11->6) +/// +/// --------------------------------------------------------- +/// z z +/// 17──────────────=10 (17->11)─────────(10->5) +/// │ _-‾ │ │ ⟋ │ +/// │ __-‾ │ │ ⟋ │ +/// │ _-‾ │ │ ⟋ │ +/// │ _-‾ │ -> │ ⟋ │ +/// 14=-════════════4,7 │ ⟋ │ +/// │ ___--‾‾ │ │ ⟋ │ +/// │ __--‾‾____----2 │ ⟋ │ +/// y 11=======────────0 y (11->6)────────────0 +/// +/// + +TEST_F(SmootherTest, smoothPointscontour) +{ + Mesh mesh = buildMeshWithInnerDent(); + const Elements& elements = mesh.groups[0].elements; + + SmootherOptions smootherOpts; + smootherOpts.featureDetectionAngle = 30; + smootherOpts.contourAlignmentAngle = 0; + + ASSERT_TRUE(meshTools::isAClosedTopology(mesh.groups[0].elements)); + + Coordinates expectedCoordinates = { + Coordinate({ 0.00, 0.00, 0.00 }), // 0 -> 0 + Coordinate({ 0.60, 0.00, 0.00 }), // 1 -> 1 + Coordinate({ 0.20, 0.00, 0.35 }), // 3 -> 2 + Coordinate({ 1.00, 0.00, 0.35 }), // 5 -> 3 + Coordinate({ 1.00, 0.00, 1.00 }), // 9 -> 4 + Coordinate({ 0.00, 0.00, 1.00 }), // 10 -> 5 + Coordinate({ 0.00, 1.00, 0.00 }), // 11 -> 6 + Coordinate({ 0.60, 1.00, 0.00 }), // 12 -> 7 + Coordinate({ 0.20, 1.00, 0.35 }), // 13 -> 8 + Coordinate({ 1.00, 1.00, 0.35 }), // 15 -> 9 + Coordinate({ 1.00, 1.00, 1.00 }), // 16 -> 10 + Coordinate({ 0.00, 1.00, 1.00 }), // 17 -> 11 + }; + + + Elements expectedElements = { + Element({ 4, 2, 3 }, Element::Type::Surface), // 0 + Element({ 2, 5, 0 }, Element::Type::Surface), // 1 + Element({ 1, 2, 0 }, Element::Type::Surface), // 2 + Element({ 4, 5, 2 }, Element::Type::Surface), // 3 + + Element({ 8, 10, 9 }, Element::Type::Surface), // 4 + Element({ 11, 8, 6 }, Element::Type::Surface), // 5 + Element({ 8, 7, 6 }, Element::Type::Surface), // 6 + Element({ 11, 10, 8 }, Element::Type::Surface), // 7 + + Element({ 1, 7, 8 }, Element::Type::Surface), // 8 + Element({ 1, 8, 2 }, Element::Type::Surface), // 9 + Element({ 4, 9, 10 }, Element::Type::Surface), // 10 + Element({ 9, 4, 3 }, Element::Type::Surface), // 11 + + Element({ 6, 5, 11 }, Element::Type::Surface), // 12 + Element({ 5, 6, 0 }, Element::Type::Surface), // 13 + + + Element({ 0, 6, 1 }, Element::Type::Surface), // 14 + Element({ 1, 6, 7 }, Element::Type::Surface), // 15 + Element({ 2, 8, 3 }, Element::Type::Surface), // 16 + Element({ 3, 8, 9 }, Element::Type::Surface), // 17 + + Element({ 4, 10, 11 }, Element::Type::Surface), // 18 + Element({ 4, 11, 5 }, Element::Type::Surface), // 19 + }; + + Mesh result = Smoother(mesh, smootherOpts).getMesh(); + + + assertCoordinatesListEquals(expectedCoordinates, result.coordinates); + assertElementsListEquals(expectedElements, result.groups[0].elements); +} + TEST_F(SmootherTest, preserves_topological_closedness_for_alhambra) { From 819e776f4f013df3fd5edc6308a6f069955cf03a Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Wed, 17 Dec 2025 18:33:24 +0100 Subject: [PATCH 15/35] WIP | DRAFT | Use delaunator in smoother --- src/core/Delaunator.cpp | 65 ++++++++++++++------------------------ src/core/Delaunator.h | 1 - src/core/Smoother.cpp | 19 ++++++----- src/core/SmootherTools.cpp | 27 ++++++++-------- test/core/SmootherTest.cpp | 60 +++++++++++++++++++++-------------- 5 files changed, 85 insertions(+), 87 deletions(-) diff --git a/src/core/Delaunator.cpp b/src/core/Delaunator.cpp index 8772259..6ee5e57 100644 --- a/src/core/Delaunator.cpp +++ b/src/core/Delaunator.cpp @@ -47,19 +47,35 @@ Delaunator::Triangulation Delaunator::buildCDT( const Polygons& constrainingPolygons) const { Triangulation cdt; - auto widestAxes = findWidestAxes(targetVertices); - std::vector newVertices; newVertices.reserve(targetVertices.size()); - for (CoordinateId v : targetVertices) { - const auto coordinate = globalCoordinates_->at(v); + Coordinates rotatedCoordinates; + std::vector originalIds; + for (auto const& id : targetVertices) { + rotatedCoordinates.push_back((*globalCoordinates_)[id]); + originalIds.push_back(id); + } - Point point({ coordinate[widestAxes.first], coordinate[widestAxes.second] }); - newVertices.push_back(point); + VecD normal({ 0.,0.,0. }); + + for (std::size_t i = 0; i + 2 < rotatedCoordinates.size() && normal.norm() == 0.0; ++i) { + for (std::size_t j = i + 1; j + 1 < rotatedCoordinates.size() && normal.norm() == 0.0; ++j) { + for (std::size_t k = j + 1; k < rotatedCoordinates.size() && normal.norm() == 0.0; ++k) { + normal = utils::Geometry::normal(TriV({ rotatedCoordinates[i], rotatedCoordinates[j], rotatedCoordinates[k] })); + } + } + } + + utils::Geometry::rotateToXYPlane(rotatedCoordinates.begin(), rotatedCoordinates.end(), normal); - PointId pointId(newVertices.size() - 1); - pointsToIds.insert(IndexPointToId::value_type(pointId, v)); + IndexPointToId res; + for (std::size_t index = 0; index < targetVertices.size(); ++index) { + Point point({ rotatedCoordinates[index][X], rotatedCoordinates[index][Y] }); + newVertices.push_back(point); + + PointId pointId(index); + pointsToIds.insert(IndexPointToId::value_type(pointId, originalIds[index])); } cdt.insertVertices(newVertices); @@ -83,39 +99,6 @@ Delaunator::Triangulation Delaunator::buildCDT( return cdt; } -std::pair Delaunator::findWidestAxes(const IdSet& targetVertexes) const { - auto start = targetVertexes.begin(); - Coordinate lowestValues = globalCoordinates_->at(*start); - Coordinate highestValues = lowestValues; - - auto vIt = std::next(start); - for (vIt; vIt != targetVertexes.end(); ++vIt) { - const Coordinate& coordinate = globalCoordinates_->at(*vIt); - - for (Axis axis = X; axis <= Z; ++axis) { - if (coordinate[axis] < lowestValues[axis]) { - lowestValues[axis] = coordinate[axis]; - } - if (coordinate[axis] > highestValues[axis]) { - highestValues[axis] = coordinate[axis]; - } - } - } - - auto distance = highestValues - lowestValues; - Axis shortestDistance = X; - for (Axis axis = Y; axis <= Z; ++axis) { - if (distance[axis] < distance[shortestDistance]) { - shortestDistance = axis; - } - } - std::set orderedAxes({ (shortestDistance + 1) % 3, (shortestDistance + 2) % 3 }); - Axis first = *orderedAxes.begin(); - Axis second = *std::next(orderedAxes.begin()); - - return std::make_pair(first, second); -} - Elements Delaunator::convertFromCDT(const Triangulation& cdt, const IndexPointToId& pointToId) const { auto newTriangles = cdt.triangles; diff --git a/src/core/Delaunator.h b/src/core/Delaunator.h index 60d2dc2..944a3b3 100644 --- a/src/core/Delaunator.h +++ b/src/core/Delaunator.h @@ -31,7 +31,6 @@ class Delaunator { void checkConstraintsArePlanar(const IdSet& targetVertices) const; Triangulation buildCDT(IndexPointToId& pointToId, const IdSet& targetVertices, const Polygons& constrainingPolygons) const; - std::pair findWidestAxes(const IdSet& targetVertexes) const; Elements convertFromCDT(const Triangulation& cdt, const IndexPointToId& pointToId) const; }; diff --git a/src/core/Smoother.cpp b/src/core/Smoother.cpp index a8696aa..ea48e3e 100644 --- a/src/core/Smoother.cpp +++ b/src/core/Smoother.cpp @@ -27,7 +27,7 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : meshTools::checkNoCellsAreCrossed(mesh); mesh_ = mesh; - mesh_ = meshTools::duplicateCoordinatesUsedByDifferentGroups(mesh_); + // mesh_ = meshTools::duplicateCoordinatesUsedByDifferentGroups(mesh_); mesh_ = meshTools::duplicateCoordinatesSharedBySingleTrianglesVertex(mesh_); Mesh res = mesh_; @@ -42,15 +42,16 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : patchs.push_back(p); } } - + /**/ std::for_each(patchs.begin(), patchs.end(), [&](auto& p) { sT_.remeshBoundary(g.elements, res.coordinates, mesh_.coordinates, p); }); - + /**/ + std::for_each(patchs.begin(), patchs.end(), [&](auto& p) { sT_.collapsePointsOnCellEdges(res.coordinates, p, singularIds, opts_.contourAlignmentAngle); }); - + /**/ std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, @@ -58,7 +59,7 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : patchs.begin(), patchs.end(), [&](auto& p) { sT_.collapsePointsOnCellFaces(res.coordinates, p, singularIds); }); - + /**/ std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, @@ -66,7 +67,7 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : patchs.begin(), patchs.end(), [&](auto& p) { sT_.collapsePointsOnFeatureEdges(res.coordinates, p, singularIds); }); - + /**/ std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, @@ -93,15 +94,17 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : redundancyCleaner::cleanCoords(res); mesh_ = res; - + /* Coordinates& cs = mesh_.coordinates; for (auto const& g : mesh_.groups) { cs = sT_.collapsePointsOnContour(g.elements, cs, opts_.contourAlignmentAngle); } + /* redundancyCleaner::fuseCoords(mesh_); redundancyCleaner::removeDegenerateElements(mesh_); - + /* meshTools::checkNoCellsAreCrossed(mesh_); + /**/ } } diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index ee2b88f..dd97a56 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -440,6 +440,7 @@ Coordinates SmootherTools::collapsePointsOnContour( const double alignmentThresholdAngle) { Coordinates res{ coords }; + auto const singularIds = buildSingularIds(elems, coords, alignmentThresholdAngle); auto contourIds{ CoordGraph{ elems }.getBoundaryGraph().getVertices() }; for (auto const& c : buildCellElemMap(elems, coords)) { @@ -499,23 +500,27 @@ void SmootherTools::collapsePointsOnContourWithDelanautor( auto cPolygons = g.getBoundaryGraph().findCycles(); std::vector newCPolygons; - std::map neighbours; + /* + std::map neighbours; + /**/ newCPolygons.reserve(cPolygons.size()); - - bool equalCycles = true; - + // Important: FeatureIds, for (const auto& cycle : cPolygons) { CoordinateIds newCycle; + for (CoordinateId v : cycle) { - if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) != sIds.cornerIds().end()) { + if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) != sIds.cornerIds().end() || isRelativeInCellCorner(coords[v])) { newCycle.push_back(v); } - else { - equalCycles = false; - } } + if (newCycle.size() <= 2) { + newCPolygons.push_back(cycle); + continue; + } + + /* std::size_t nextNeighbourIndex = 0; std::size_t previousNeighbourIndex = newCycle.size() - 1; @@ -529,14 +534,10 @@ void SmootherTools::collapsePointsOnContourWithDelanautor( nextNeighbourIndex = (nextNeighbourIndex + 1) % newCycle.size(); } } - + /**/ newCPolygons.push_back(newCycle); } - if (equalCycles) { - return; - } - Elements remeshedEls = delaunator_.mesh(newCPolygons); for (auto& element : remeshedEls) { diff --git a/test/core/SmootherTest.cpp b/test/core/SmootherTest.cpp index f2e2d70..ff6f298 100644 --- a/test/core/SmootherTest.cpp +++ b/test/core/SmootherTest.cpp @@ -238,11 +238,11 @@ TEST_F(SmootherTest, touching_by_single_point) /// 9──────────────=16 (9->4)─────────(16->10) /// │ _-‾ │ │ ‾-_ │ /// │ __-‾ │ │ ‾-__ │ -/// │ _-‾ │ │ ‾-_ │ -/// │ _-‾ │ -> │ ‾-_ │ +/// │ _-‾ │ -> │ ‾-_ │ +/// │ _-‾ │ │ ‾-_ │ /// 3,5,8══════════-13,15 (3->2),(5->3)─────────(13->8),(15->9) -/// │ ___--‾‾ │ │ ___--‾‾ │ -/// │ __--‾‾ │ │ __--‾‾ │ +/// │ ___--‾‾ │ │ ‾‾--___ │ +/// │ __--‾‾ │ │ ‾‾--__ │ /// 1=──────────────12 y 1=───────────(12->7) y /// /// @@ -271,6 +271,18 @@ TEST_F(SmootherTest, touching_by_single_point) /// │ __--‾‾____----2 │ ⟋ │ /// y 11=======────────0 y (11->6)────────────0 /// +/// -------------------------------------------------------------------- +/// +/// 9───────────────10 (9->4)─────────(10->5) +/// │ ⟍ │ │ ⟋ ⎸ +/// │ ⟍ │ │ ⟋ │ +/// │ ⟍ │ │ ⟋ │ +/// │ ⟍ │ -> │ ⟋ │ +/// │ ⟍ │ │ ⟋ │ +/// │ ⟍ │ │ ⟋ │ +/// │ ⟍ ⎸ │ ⟋ │ +/// x 16───────────────17 x (16->10)─────────(17->11) +/// y y /// TEST_F(SmootherTest, smoothPointscontour) @@ -301,32 +313,32 @@ TEST_F(SmootherTest, smoothPointscontour) Elements expectedElements = { - Element({ 4, 2, 3 }, Element::Type::Surface), // 0 - Element({ 2, 5, 0 }, Element::Type::Surface), // 1 - Element({ 1, 2, 0 }, Element::Type::Surface), // 2 - Element({ 4, 5, 2 }, Element::Type::Surface), // 3 + Element({ 4, 2, 3 }, Element::Type::Surface), // 0 + Element({ 2, 5, 0 }, Element::Type::Surface), // 1 + Element({ 1, 2, 0 }, Element::Type::Surface), // 2 + Element({ 4, 5, 2 }, Element::Type::Surface), // 3 - Element({ 8, 10, 9 }, Element::Type::Surface), // 4 - Element({ 11, 8, 6 }, Element::Type::Surface), // 5 - Element({ 8, 7, 6 }, Element::Type::Surface), // 6 - Element({ 11, 10, 8 }, Element::Type::Surface), // 7 + Element({ 8, 10, 9 }, Element::Type::Surface), // 4 + Element({ 11, 8, 6 }, Element::Type::Surface), // 5 + Element({ 8, 7, 6 }, Element::Type::Surface), // 6 + Element({ 11, 10, 8 }, Element::Type::Surface), // 7 - Element({ 1, 7, 8 }, Element::Type::Surface), // 8 - Element({ 1, 8, 2 }, Element::Type::Surface), // 9 - Element({ 4, 9, 10 }, Element::Type::Surface), // 10 - Element({ 9, 4, 3 }, Element::Type::Surface), // 11 + Element({ 8, 2, 7 }, Element::Type::Surface), // 8 + Element({ 1, 7, 2 }, Element::Type::Surface), // 9 + Element({ 10, 4, 9 }, Element::Type::Surface), // 10 + Element({ 9, 4, 3 }, Element::Type::Surface), // 11 - Element({ 6, 5, 11 }, Element::Type::Surface), // 12 - Element({ 5, 6, 0 }, Element::Type::Surface), // 13 + Element({ 6, 5, 11 }, Element::Type::Surface), // 12 + Element({ 5, 6, 0 }, Element::Type::Surface), // 13 - Element({ 0, 6, 1 }, Element::Type::Surface), // 14 - Element({ 1, 6, 7 }, Element::Type::Surface), // 15 - Element({ 2, 8, 3 }, Element::Type::Surface), // 16 - Element({ 3, 8, 9 }, Element::Type::Surface), // 17 + Element({ 7, 1, 6 }, Element::Type::Surface), // 14 + Element({ 6, 1, 0 }, Element::Type::Surface), // 15 + Element({ 9, 3, 8 }, Element::Type::Surface), // 16 + Element({ 8, 3, 2 }, Element::Type::Surface), // 17 - Element({ 4, 10, 11 }, Element::Type::Surface), // 18 - Element({ 4, 11, 5 }, Element::Type::Surface), // 19 + Element({ 10, 5, 4 }, Element::Type::Surface), // 18 + Element({ 5, 10, 11 }, Element::Type::Surface), // 19 }; Mesh result = Smoother(mesh, smootherOpts).getMesh(); From 9b23db4e8345ccec86f5e2967ffb963374585332 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Fri, 19 Dec 2025 15:03:37 +0100 Subject: [PATCH 16/35] WIP | DRAFT | Fix delaunator repeated vertex issue --- src/core/Delaunator.cpp | 38 +++++++++++++++++++++++--------------- src/core/Delaunator.h | 18 +++++++++++++++--- src/core/Smoother.cpp | 4 ++-- src/core/Smoother.h | 1 - 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/core/Delaunator.cpp b/src/core/Delaunator.cpp index 6ee5e57..af14d0b 100644 --- a/src/core/Delaunator.cpp +++ b/src/core/Delaunator.cpp @@ -15,7 +15,7 @@ Elements Delaunator::mesh(const Polygons& constrainingPolygons) const { checkConstraintsArePlanar(targetVertices); - IndexPointToId pointsToIds; + PointToId pointsToIds; Triangulation cdt = buildCDT(pointsToIds, targetVertices, constrainingPolygons); @@ -42,14 +42,11 @@ void Delaunator::checkConstraintsArePlanar(const IdSet & targetVertices) const { } Delaunator::Triangulation Delaunator::buildCDT( - IndexPointToId& pointsToIds, + PointToId& pointsToIds, const IdSet& targetVertices, const Polygons& constrainingPolygons) const { Triangulation cdt; - std::vector newVertices; - newVertices.reserve(targetVertices.size()); - Coordinates rotatedCoordinates; std::vector originalIds; for (auto const& id : targetVertices) { @@ -69,13 +66,20 @@ Delaunator::Triangulation Delaunator::buildCDT( utils::Geometry::rotateToXYPlane(rotatedCoordinates.begin(), rotatedCoordinates.end(), normal); - IndexPointToId res; + for (std::size_t index = 0; index < targetVertices.size(); ++index) { Point point({ rotatedCoordinates[index][X], rotatedCoordinates[index][Y] }); - newVertices.push_back(point); PointId pointId(index); - pointsToIds.insert(IndexPointToId::value_type(pointId, originalIds[index])); + pointsToIds.insert(PointToId::value_type(point, originalIds[index])); + } + + std::vector newVertices; + newVertices.reserve(pointsToIds.left.size()); + + for (auto& leftPointIt = pointsToIds.left.begin(); leftPointIt != pointsToIds.left.end(); ++leftPointIt) { + const Point& point = leftPointIt->first; + newVertices.push_back(point); } cdt.insertVertices(newVertices); @@ -85,12 +89,15 @@ Delaunator::Triangulation Delaunator::buildCDT( for (const auto& polygon : constrainingPolygons) { for (std::size_t i = 0; i < polygon.size(); ++i) { - std::set edgeSet({ - pointsToIds.right.at(polygon[i]), - pointsToIds.right.at(polygon[(i + 1) % polygon.size()]) - }); + auto leftCoordIdIt = pointsToIds.right.find(polygon[i]); + auto leftPointIt = pointsToIds.project_left(leftCoordIdIt); + PointId leftId = PointId(std::distance(pointsToIds.left.begin(), leftPointIt)); + + auto rightCoordIdIt = pointsToIds.right.find(polygon[(i + 1) % polygon.size()]); + auto rightPointIt = pointsToIds.project_left(rightCoordIdIt); + PointId rightId = PointId(std::distance(pointsToIds.left.begin(), rightPointIt)); - edges.emplace_back(*edgeSet.begin(), *edgeSet.rbegin()); + edges.emplace_back(leftId, rightId); } } @@ -99,7 +106,7 @@ Delaunator::Triangulation Delaunator::buildCDT( return cdt; } -Elements Delaunator::convertFromCDT(const Triangulation& cdt, const IndexPointToId& pointToId) const { +Elements Delaunator::convertFromCDT(const Triangulation& cdt, const PointToId& pointToId) const { auto newTriangles = cdt.triangles; Elements result; @@ -110,7 +117,8 @@ Elements Delaunator::convertFromCDT(const Triangulation& cdt, const IndexPointTo vertices.reserve(3); for (PointId id : triangle.vertices) { - vertices.push_back(pointToId.left.at(id)); + const Point& point = cdt.vertices[id]; + vertices.push_back(pointToId.left.at(point)); } result.emplace_back(vertices, Element::Type::Surface); diff --git a/src/core/Delaunator.h b/src/core/Delaunator.h index 944a3b3..4393a18 100644 --- a/src/core/Delaunator.h +++ b/src/core/Delaunator.h @@ -27,11 +27,23 @@ class Delaunator { typedef CDT::V2d Point; typedef CDT::VertInd PointId; - typedef boost::bimap IndexPointToId; + struct PointLesserComparer { + bool operator() (const Point& pointA, const Point& pointB) const noexcept { + if (pointA.x < pointB.x) { + return true; + } + if (pointA.x == pointB.x) { + return pointA.y < pointB.y; + } + return false; + } + }; + + typedef boost::bimap, CoordinateId> PointToId; void checkConstraintsArePlanar(const IdSet& targetVertices) const; - Triangulation buildCDT(IndexPointToId& pointToId, const IdSet& targetVertices, const Polygons& constrainingPolygons) const; - Elements convertFromCDT(const Triangulation& cdt, const IndexPointToId& pointToId) const; + Triangulation buildCDT(PointToId& pointToId, const IdSet& targetVertices, const Polygons& constrainingPolygons) const; + Elements convertFromCDT(const Triangulation& cdt, const PointToId& pointToId) const; }; } diff --git a/src/core/Smoother.cpp b/src/core/Smoother.cpp index ea48e3e..9994d2d 100644 --- a/src/core/Smoother.cpp +++ b/src/core/Smoother.cpp @@ -21,16 +21,16 @@ using namespace meshTools; Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : - sT_(SmootherTools(mesh.grid, mesh.coordinates)), opts_(opts) { meshTools::checkNoCellsAreCrossed(mesh); mesh_ = mesh; - // mesh_ = meshTools::duplicateCoordinatesUsedByDifferentGroups(mesh_); + mesh_ = meshTools::duplicateCoordinatesUsedByDifferentGroups(mesh_); mesh_ = meshTools::duplicateCoordinatesSharedBySingleTrianglesVertex(mesh_); Mesh res = mesh_; + SmootherTools sT_(res.grid, res.coordinates); for (auto& g : res.groups) { auto const singularIds = sT_.buildSingularIds(g.elements, mesh_.coordinates, opts_.featureDetectionAngle); diff --git a/src/core/Smoother.h b/src/core/Smoother.h index 7bc2bb4..bcf03be 100644 --- a/src/core/Smoother.h +++ b/src/core/Smoother.h @@ -19,7 +19,6 @@ class Smoother { private: SmootherOptions opts_; - SmootherTools sT_; Mesh mesh_; Mesh orient(const Mesh& mesh) const; From 23fe6d18b16cd1319182f871cb00daff07bb0c79 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Mon, 12 Jan 2026 18:11:06 +0100 Subject: [PATCH 17/35] WIP | DRAFT | Use delaunator in smoother --- src/core/SmootherTools.cpp | 19 ++++++++++ test/core/SmootherTest.cpp | 74 ++++++++++++++++++++++++++------------ 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index dd97a56..67af890 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -515,6 +515,25 @@ void SmootherTools::collapsePointsOnContourWithDelanautor( } } + if (newCycle.size() <= 2) { + newCycle.clear(); + + auto previousIt = cycle.begin(); + + newCycle.push_back(*previousIt); + Coordinate * previousCoordinate = &coords[*previousIt]; + + for (auto currentIt = std::next(previousIt); currentIt != cycle.end(); ++currentIt) { + if (coords[*currentIt] != *previousCoordinate) { + if (std::next(currentIt) != cycle.end() || coords[*currentIt] != coords[newCycle[0]]) { + newCycle.push_back(*currentIt); + previousIt = currentIt; + previousCoordinate = &coords[*currentIt]; + } + } + } + } + if (newCycle.size() <= 2) { newCPolygons.push_back(cycle); continue; diff --git a/test/core/SmootherTest.cpp b/test/core/SmootherTest.cpp index ff6f298..7f234eb 100644 --- a/test/core/SmootherTest.cpp +++ b/test/core/SmootherTest.cpp @@ -233,21 +233,7 @@ TEST_F(SmootherTest, touching_by_single_point) /// 2‾‾--__‾-_ │/ ‾-_ /// 0──────====1 x 0─────────=1 x /// -/// -------------------------------------------------------- -/// z z -/// 9──────────────=16 (9->4)─────────(16->10) -/// │ _-‾ │ │ ‾-_ │ -/// │ __-‾ │ │ ‾-__ │ -/// │ _-‾ │ -> │ ‾-_ │ -/// │ _-‾ │ │ ‾-_ │ -/// 3,5,8══════════-13,15 (3->2),(5->3)─────────(13->8),(15->9) -/// │ ___--‾‾ │ │ ‾‾--___ │ -/// │ __--‾‾ │ │ ‾‾--__ │ -/// 1=──────────────12 y 1=───────────(12->7) y -/// -/// /// ------------------------------------------------------------------ -/// /// z z /// 16=──────────────17 (16->10)─────────(17->11) /// │ ⟍‾-_ │ │ ⟍ /│ @@ -259,6 +245,18 @@ TEST_F(SmootherTest, touching_by_single_point) /// _-‾ ‾\│ _-‾ ‾\│ /// x 12──────────11 x (12->7)────(11->6) /// +/// -------------------------------------------------------- +/// z z +/// 9──────────────=16 (9->4)─────────(16->10) +/// │ _-‾ │ │ ‾-_ │ +/// │ __-‾ │ │ ‾-__ │ +/// │ _-‾ │ -> │ ‾-_ │ +/// │ _-‾ │ │ ‾-_ │ +/// 3,5,8══════════-13,15 (3->2),(5->3)─────────(13->8),(15->9) +/// │ ___--‾‾ │ │ ‾‾--___ │ +/// │ __--‾‾ │ │ ‾‾--__ │ +/// 1=──────────────12 y 1=───────────(12->7) y +/// /// --------------------------------------------------------- /// z z /// 17──────────────=10 (17->11)─────────(10->5) @@ -271,6 +269,19 @@ TEST_F(SmootherTest, touching_by_single_point) /// │ __--‾‾____----2 │ ⟋ │ /// y 11=======────────0 y (11->6)────────────0 /// +/// --------------------------------------------------------- +/// z +/// 0────3─────1────5 x 0─(3->2)───1──(5->3) +/// │ . /│ / │ │ . /│ / │ +/// │ . / │ / │ │ . / │ / │ +/// │ . / │/ │ │ . / │/ │ +/// │ ./ .│ │ -> │ ./ .│ │ +/// │ /. . │ │ │ /. . │ │ +/// │ / . . │ │ │ / . . │ │ +/// │/ .. │ │ │/ .. │ │ +/// 11────13────12───15 (11->6)───*─(12->7)─(15->9) +/// (13->8) +/// /// -------------------------------------------------------------------- /// /// 9───────────────10 (9->4)─────────(10->5) @@ -337,8 +348,8 @@ TEST_F(SmootherTest, smoothPointscontour) Element({ 9, 3, 8 }, Element::Type::Surface), // 16 Element({ 8, 3, 2 }, Element::Type::Surface), // 17 - Element({ 10, 5, 4 }, Element::Type::Surface), // 18 - Element({ 5, 10, 11 }, Element::Type::Surface), // 19 + Element({ 4, 11, 5 }, Element::Type::Surface), // 18 + Element({ 11, 4, 10 }, Element::Type::Surface), // 19 }; Mesh result = Smoother(mesh, smootherOpts).getMesh(); @@ -354,10 +365,26 @@ TEST_F(SmootherTest, preserves_topological_closedness_for_alhambra) auto m = vtkIO::readInputMesh("testData/cases/alhambra/alhambra.stl"); EXPECT_TRUE(meshTools::isAClosedTopology(m.groups[0].elements)); + /* + m.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 61); m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 61); m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 8); + + /* + + m.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 16); + m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 16); + m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 2); + + /**/ + m.grid[X] = utils::GridTools::linspace(-60.0, -36.0, 4); + m.grid[Y] = utils::GridTools::linspace(-52.0, -28.0, 4); + m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 2); + + /**/ + auto slicedMesh = Slicer{m}.getMesh(); SmootherOptions smootherOpts; @@ -369,12 +396,15 @@ TEST_F(SmootherTest, preserves_topological_closedness_for_alhambra) EXPECT_TRUE(meshTools::isAClosedTopology(m.groups[0].elements)); EXPECT_TRUE(meshTools::isAClosedTopology(smoothedMesh.groups[0].elements)); - // //For debugging. - // meshTools::convertToAbsoluteCoordinates(smoothedMesh); - // vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.smoothed.vtk", smoothedMesh); - // - // auto contourMesh = meshTools::buildMeshFromContours(smoothedMesh); - // vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.contour.vtk", contourMesh); + //For debugging. + meshTools::convertToAbsoluteCoordinates(smoothedMesh); + + + vtkIO::exportGridToVTU("testData/cases/alhambra/alhambra.grid.vtk", smoothedMesh.grid); + vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.smoothed.vtk", smoothedMesh); + + auto contourMesh = meshTools::buildMeshFromContours(smoothedMesh); + vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.contour.vtk", contourMesh); } From a05bb1c6ae76c88d660a053746d57fca548d3e05 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Tue, 13 Jan 2026 18:16:07 +0100 Subject: [PATCH 18/35] WIP | DRAFT | Use delaunator in smoother --- src/core/SmootherTools.cpp | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index 67af890..aa67e3e 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -508,9 +508,13 @@ void SmootherTools::collapsePointsOnContourWithDelanautor( // Important: FeatureIds, for (const auto& cycle : cPolygons) { CoordinateIds newCycle; + + if (cycle.size() <= 2) { + continue; + } for (CoordinateId v : cycle) { - if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) != sIds.cornerIds().end() || isRelativeInCellCorner(coords[v])) { + if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) != sIds.cornerIds().end() || isRelativeInCellEdge(coords[v])) { newCycle.push_back(v); } } @@ -518,25 +522,23 @@ void SmootherTools::collapsePointsOnContourWithDelanautor( if (newCycle.size() <= 2) { newCycle.clear(); - auto previousIt = cycle.begin(); + newCycle.insert(newCycle.begin(), cycle.begin(), cycle.end()); + } - newCycle.push_back(*previousIt); - Coordinate * previousCoordinate = &coords[*previousIt]; + auto currentIt = newCycle.begin(); - for (auto currentIt = std::next(previousIt); currentIt != cycle.end(); ++currentIt) { - if (coords[*currentIt] != *previousCoordinate) { - if (std::next(currentIt) != cycle.end() || coords[*currentIt] != coords[newCycle[0]]) { - newCycle.push_back(*currentIt); - previousIt = currentIt; - previousCoordinate = &coords[*currentIt]; - } - } + while (currentIt != newCycle.end()) { + auto nextIt = std::next(currentIt); + if (nextIt == newCycle.end()) { + nextIt = newCycle.begin(); } - } - if (newCycle.size() <= 2) { - newCPolygons.push_back(cycle); - continue; + if (coords[*currentIt] == coords[*nextIt]) { + currentIt = newCycle.erase(currentIt); + } + else { + ++currentIt; + } } /* @@ -557,6 +559,10 @@ void SmootherTools::collapsePointsOnContourWithDelanautor( newCPolygons.push_back(newCycle); } + if (newCPolygons.size() == 0) { + return; + } + Elements remeshedEls = delaunator_.mesh(newCPolygons); for (auto& element : remeshedEls) { From 9b082481656cb64b644e0779186e8bfec1bc1041 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Wed, 14 Jan 2026 16:15:25 +0100 Subject: [PATCH 19/35] WIP | DRAFT | Fix bug mistaking a parallel plane for a face while comparing two points in greed tools --- src/utils/GridTools.cpp | 16 +- test/utils/GridToolsTest.cpp | 342 +++++++++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+), 5 deletions(-) diff --git a/src/utils/GridTools.cpp b/src/utils/GridTools.cpp index 2e282ad..6ac9ec6 100644 --- a/src/utils/GridTools.cpp +++ b/src/utils/GridTools.cpp @@ -505,19 +505,25 @@ std::size_t GridTools::countIntersectingPlanes(const Relative& v) { bool GridTools::areCoordOnSameFace(const Relative& r1, const Relative& r2) { - // This assumes that both relatives belong to the same cell. + Cell cell1 = toCell(r1); + Cell cell2 = toCell(r2); + if (isRelativeInterior(r1) || isRelativeInterior(r2)) { return false; } - std::size_t nEqualCoords = 0; + if (cell1 != cell2) { + return false; + } + + std::size_t nEqualCoordsInFace = 0; for (Axis d = 0; d < 3; d++) { - if (approxDir(r1(d) - r2(d), 0.0)) { - nEqualCoords++; + if (approxDir(cell1[d], r1(d)) && approxDir(r1(d) - r2(d), 0.0)) { + nEqualCoordsInFace++; } } - if (nEqualCoords >= 1){ + if (nEqualCoordsInFace >= 1){ return true; } else { diff --git a/test/utils/GridToolsTest.cpp b/test/utils/GridToolsTest.cpp index 90d1eb1..5cb87be 100644 --- a/test/utils/GridToolsTest.cpp +++ b/test/utils/GridToolsTest.cpp @@ -219,6 +219,348 @@ TEST_F(GridToolsTest, getTouchingCells) { } +TEST_F(GridToolsTest, commonPropertiesBetweenCoordinates) { + Grid grid = GridTools::buildCartesianGrid(0, 5, 6); + GridTools tools = GridTools(grid); + + // Interior in different cells + { + Relative r1 = { 0.25, 0.30, 0.65 }; + Relative r2 = { 1.25, 2.30, 3.65 }; + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // Interior in same cell + { + Relative r1 = { 0.25, 1.30, 2.65 }; + Relative r2 = { 0.35, 1.50, 2.10 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // One in Face and another is interior + { + Relative r1 = { 2.00, 1.30, 2.65 }; + Relative r2 = { 2.35, 1.50, 2.10 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r2, r1)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r2, r1)); + EXPECT_FALSE(tools.isSegmentOnFace(r2, r1)); + EXPECT_FALSE(tools.isSegmentOnEdge(r2, r1)); + } + + // One in Edge and another is interior + { + Relative r1 = { 2.00, 1.00, 2.65 }; + Relative r2 = { 2.35, 1.50, 2.10 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r2, r1)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r2, r1)); + EXPECT_FALSE(tools.isSegmentOnFace(r2, r1)); + EXPECT_FALSE(tools.isSegmentOnEdge(r2, r1)); + } + + // One in Corner and another is interior + { + Relative r1 = { 2.00, 1.00, 2.00 }; + Relative r2 = { 2.35, 1.50, 2.10 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r2, r1)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r2, r1)); + EXPECT_FALSE(tools.isSegmentOnFace(r2, r1)); + EXPECT_FALSE(tools.isSegmentOnEdge(r2, r1)); + } + + // One in Edge and another in Face in different cells + { + Relative r1 = { 2.00, 1.00, 0.30 }; + Relative r2 = { 2.35, 1.50, 2.00 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // One in Corner and another in Face in different cells + { + Relative r1 = { 2.00, 1.00, 0.00 }; + Relative r2 = { 2.35, 1.50, 2.00 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // One in Corner and another in Edge in different cells + { + Relative r1 = { 2.00, 1.00, 0.00 }; + Relative r2 = { 2.35, 3.00, 2.00 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y + // *-----------------*-----------------*-----------------* + // | | | | + // | | | 2* | + // | | | | + // | | | | + // | | | | + // | | | | + // *-----------------*-----------------*-----------------* + // | | | | + // | | | | + // | | | | + // | | | | + // | 1* | | | + // | | | | + // *-----------------*-----------------*-----------------* x + // + // + // Two points on faces in separate cells + { + Relative r1 = { 0.40, 0.25, 0.00 }; + Relative r2 = { 2.50, 1.80, 0.00 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // | 2 | | *2 | + // | | | | | + // 1 | | | *1 | + // | | | | | + // *-----------------* x | *-----------------* x + // | + // + // Two points on opposite faces + { + Relative r1 = { 1.00, 1.25, 1.60 }; + Relative r2 = { 2.00, 1.80, 1.25 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // 2 | | 2 | + // | | | | | + // | | | | *1 | + // | | | | | + // *--------1--------* x | *-----------------* x + // | + // + // Two points on faces in different axis + { + Relative r1 = { 1.50, 1.00, 1.25 }; + Relative r2 = { 1.00, 1.70, 1.60 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // 2 | | 2 *1 | + // | | | | | + // | | | | | + // | | | | | + // *--------1--------* x | *-----------------* x + // | + // + // Two points with the same Z value, on faces in different axis + { + Relative r1 = { 1.50, 1.00, 1.60 }; + Relative r2 = { 1.00, 1.70, 1.60 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *---1-----------2-* | *-----------------* + // | | | | | + // | | | | *1 | + // | | | | | + // | | | | *2| + // | | | | | + // | | | | | + // *-----------------* x | *-----------------* x + // | + // + // Two points in the same face + { + Relative r1 = { 1.25, 2.00, 1.70 }; + Relative r2 = { 1.90, 2.00, 1.50 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // Two edges in different cells + { + Relative r1 = { 2.00, 1.00, 0.50 }; + Relative r2 = { 3.00, 4.25, 2.00 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // | *2 | | | | + // | | | 1 | + // | | | | | + // | | | | | + // 1-----------------* x | *-------2---------* x + // | + // + + // Edge and face points on the same cell + { + Relative r1 = { 1.00, 1.00, 1.45 }; + Relative r2 = { 1.40, 1.60, 1.00 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // | | | | *2 | + // | | | 1 | + // | | | | | + // | | | | | + // 1-------2---------* x | *-----------------* x + // | + // + // Edge and face points with shared Face + { + Relative r1 = { 1.00, 1.00, 1.45 }; + Relative r2 = { 1.40, 1.00, 1.60 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // | | | | | + // 1 | | | | + // | | | | | + // | | | | | + // *-------2---------* x | 1-------2---------* x + // | + // + // Two Edge Points with shared face but different edge axis + { + Relative r1 = { 1.00, 1.45, 1.00 }; + Relative r2 = { 1.40, 1.00, 1.00 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } +} + TEST_F(GridToolsTest, uniformDualGrid) { Grid grid; From d033e4009694fad429104e0169defac1a28b69e0 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Wed, 14 Jan 2026 17:38:58 +0100 Subject: [PATCH 20/35] WIP | DRAFT | Use delaunator in smoother --- src/core/SmootherTools.cpp | 63 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index aa67e3e..4bc46a8 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -509,53 +509,54 @@ void SmootherTools::collapsePointsOnContourWithDelanautor( for (const auto& cycle : cPolygons) { CoordinateIds newCycle; - if (cycle.size() <= 2) { + if (cycle.size() < 3) { continue; } - for (CoordinateId v : cycle) { - if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) != sIds.cornerIds().end() || isRelativeInCellEdge(coords[v])) { - newCycle.push_back(v); + CoordinateIds filteredRepeated; + filteredRepeated.reserve(cycle.size()); + + + for (std::size_t currentIndex = 0; currentIndex < cycle.size(); ++currentIndex) { + auto nextIndex = (currentIndex + 1) % cycle.size(); + + if (coords[cycle[currentIndex]] != coords[cycle[nextIndex]]) { + filteredRepeated.push_back(cycle[currentIndex]); } } - if (newCycle.size() <= 2) { - newCycle.clear(); - - newCycle.insert(newCycle.begin(), cycle.begin(), cycle.end()); + if (filteredRepeated.size() < 3) { + continue; } - auto currentIt = newCycle.begin(); + Relative center = (coords[filteredRepeated[0]] + coords[filteredRepeated[1]] + coords[filteredRepeated[2]]) / 3; + Cell centerCell = toCell(center); - while (currentIt != newCycle.end()) { - auto nextIt = std::next(currentIt); - if (nextIt == newCycle.end()) { - nextIt = newCycle.begin(); - } + bool isCycleOnSameFace = true; + std::size_t i = 0; + while (isCycleOnSameFace && i < filteredRepeated.size()) { + isCycleOnSameFace = toCell(coords[filteredRepeated[i]]) == centerCell && areCoordOnSameFace(center, coords[filteredRepeated[i]]); + ++i; + } - if (coords[*currentIt] == coords[*nextIt]) { - currentIt = newCycle.erase(currentIt); + for (CoordinateId v : filteredRepeated) { + if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) != sIds.cornerIds().end()) { + newCycle.push_back(v); + } + else if (!isCycleOnSameFace && isRelativeInCellEdge(coords[v])) { + newCycle.push_back(v); } - else { - ++currentIt; + else if (isRelativeInCellCorner(coords[v])) { + newCycle.push_back(v); } } - /* - std::size_t nextNeighbourIndex = 0; - std::size_t previousNeighbourIndex = newCycle.size() - 1; + if (newCycle.size() < 3) { + newCycle.clear(); - for (CoordinateId v : cycle) { - if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) == sIds.cornerIds().end()) { - neighbours[v] = CoordinateIds{ newCycle[previousNeighbourIndex], newCycle[nextNeighbourIndex] }; - } - else { - neighbours[v] = CoordinateIds{ v, v }; - previousNeighbourIndex = nextNeighbourIndex; - nextNeighbourIndex = (nextNeighbourIndex + 1) % newCycle.size(); - } + newCycle.insert(newCycle.begin(), filteredRepeated.begin(), filteredRepeated.end()); } - /**/ + newCPolygons.push_back(newCycle); } From 77081e5622701ef6a4089cbc7edfd14c06197c6a Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Thu, 15 Jan 2026 12:11:56 +0100 Subject: [PATCH 21/35] WIP | DRAFT | Fix bug mistaking a parallel plane for a face while comparing two points in greed tools --- src/utils/GridTools.cpp | 34 ++-- test/utils/GridToolsTest.cpp | 337 ++++++++++++++++++++++++++++++++++- 2 files changed, 360 insertions(+), 11 deletions(-) diff --git a/src/utils/GridTools.cpp b/src/utils/GridTools.cpp index 6ac9ec6..19b27b2 100644 --- a/src/utils/GridTools.cpp +++ b/src/utils/GridTools.cpp @@ -512,18 +512,23 @@ bool GridTools::areCoordOnSameFace(const Relative& r1, const Relative& r2) return false; } - if (cell1 != cell2) { - return false; - } - std::size_t nEqualCoordsInFace = 0; + + std::size_t nEqualCoords = 0; for (Axis d = 0; d < 3; d++) { + auto minCellValue = std::min(cell1[d], cell2[d]); + auto maxCellValue = std::max(ceil(r1[d]), ceil(r2[d])); + + if ((maxCellValue - minCellValue) > 1) { + return false; + } + if (approxDir(cell1[d], r1(d)) && approxDir(r1(d) - r2(d), 0.0)) { - nEqualCoordsInFace++; + nEqualCoords++; } } - if (nEqualCoordsInFace >= 1){ + if (nEqualCoords >= 1){ return true; } else { @@ -532,14 +537,23 @@ bool GridTools::areCoordOnSameFace(const Relative& r1, const Relative& r2) } bool GridTools::areCoordOnSameEdge(const Relative& r1, const Relative& r2) { - // This assumes that both relatives belong to the same cell. + Cell cell1 = toCell(r1); + Cell cell2 = toCell(r2); + if (isRelativeInterior(r1) || isRelativeInterior(r2)) { return false; } std::size_t nEqualCoords = 0; for (Axis d = 0; d < 3; d++) { - if (approxDir(r1(d) - r2(d), 0.0)) { + auto minCellValue = std::min(cell1[d], cell2[d]); + auto maxCellValue = std::max(ceil(r1[d]), ceil(r2[d])); + + if ((maxCellValue - minCellValue) > 1) { + return false; + } + + if (approxDir(cell1[d], r1(d)) && approxDir(r1(d) - r2(d), 0.0)) { nEqualCoords++; } } @@ -718,8 +732,8 @@ bool GridTools::isSegmentOnFace( return true; } - if ((isRelativeInCellFace(r1) && isRelativeInCellEdge(r2)) && - (isRelativeInCellEdge(r1) && isRelativeInCellEdge(r2))) { + if ((isRelativeInCellFace(r1) && isRelativeInCellEdge(r2)) || + (isRelativeInCellEdge(r1) && isRelativeInCellFace(r2))) { return true; } diff --git a/test/utils/GridToolsTest.cpp b/test/utils/GridToolsTest.cpp index 5cb87be..94da874 100644 --- a/test/utils/GridToolsTest.cpp +++ b/test/utils/GridToolsTest.cpp @@ -421,6 +421,31 @@ TEST_F(GridToolsTest, commonPropertiesBetweenCoordinates) { EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); } + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // | | | | *2 | + // | *1,2 | | | | + // | | | | | + // | | | | *1 | + // *-----------------* x | *-----------------* x + // | + // + // Two interior points with segment parallel to edge + { + Relative r1 = { 1.35, 1.45, 1.20 }; + Relative r2 = { 1.35, 1.45, 1.60 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + // // y | z // *-----------------* | *-----------------* @@ -471,6 +496,31 @@ TEST_F(GridToolsTest, commonPropertiesBetweenCoordinates) { EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); } + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // | | | | 2 + // | 1,2 | | | + // | | | | | + // | | | | 1 + // *-----------------* x | *-----------------* x + // | + // + // Two face points with segment parallel to edge + { + Relative r1 = { 2.00, 1.45, 1.20 }; + Relative r2 = { 2.00, 1.45, 1.60 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + // Two edges in different cells { Relative r1 = { 2.00, 1.00, 0.50 }; @@ -531,7 +581,7 @@ TEST_F(GridToolsTest, commonPropertiesBetweenCoordinates) { EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); - EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnFace(r1, r2)); EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); } @@ -559,6 +609,291 @@ TEST_F(GridToolsTest, commonPropertiesBetweenCoordinates) { EXPECT_TRUE(tools.isSegmentOnFace(r1, r2)); EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | 2 | | | + // | | | | | + // 1 | | | | + // | | | | | + // | | | | | + // *-----------------* x | 1-----------------2 x + // | + // + // Two Edge Points with one shared face and one opposite + { + Relative r1 = { 1.00, 1.45, 1.00 }; + Relative r2 = { 2.00, 1.80, 1.00 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | 2 | | | + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // | 1 | | | + // | | | | | + // *-----------------* x | *----------------1,2 x + // | + // + // Two Edge Points in subsequent edges + { + Relative r1 = { 2.00, 1.45, 1.00 }; + Relative r2 = { 2.00, 2.80, 1.00 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | 2 | | | + // | | | | | + // | | | | | + // | 1 | | | + // | | | | | + // *-----------------* x | *----------------1,2 x + // | + // + // Two Edge Points in the same edge + { + Relative r1 = { 2.00, 1.45, 1.00 }; + Relative r2 = { 2.00, 1.80, 1.00 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_TRUE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | 2 | | | + // | | | | | + // | | | | | + // | | | | 2 + // | | | | | + // 1-----------------* x | 1-----------------* x + // | + // + // 1 Corner point and face point in opposite face + { + Relative r1 = { 1.00, 1.00, 1.00 }; + Relative r2 = { 2.00, 1.80, 1.25 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // 1-----------------* | 1-----------------* + // | | | | | + // | 2 | | | + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // *-----------------* x | *-----------------2 x + // | + // + // 1 Corner point and edge point in opposite face + { + Relative r1 = { 1.00, 2.00, 2.00 }; + Relative r2 = { 2.00, 1.75, 1.00 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------2 | *-----------------2 + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // 1-----------------* x | 1-----------------* x + // | + // + // 2 opposite corner points + { + Relative r1 = { 1.00, 1.00, 1.00 }; + Relative r2 = { 2.00, 2.00, 2.00 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_FALSE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // | | | | 2 | + // | | | | | + // | | | | | + // | | | | | + // 1---------2-------* x | 1-----------------* x + // | + // + // 1 Corner point and 1 Face point with common axis. + { + Relative r1 = { 1.00, 1.00, 1.00 }; + Relative r2 = { 1.60, 1.00, 1.70 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | 2 | | | + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // 1-----------------* x | 1-----------------2 x + // | + // + // 1 Corner point and 1 Edge point with shared face. + { + Relative r1 = { 1.00, 1.00, 1.00 }; + Relative r2 = { 2.00, 1.75, 1.00 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // 2 | | | | + // | | | | | + // | | | | | + // | | | | | + // 1-----------------* x | 1,2----------------* x + // | + // + // 1 Corner point and edge point with shared edge. + { + Relative r1 = { 1.00, 1.00, 1.00 }; + Relative r2 = { 1.00, 1.70, 1.00 }; + + EXPECT_FALSE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_TRUE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnEdge(r1, r2)); + } + + + // + // y | z + // *-----------------2 | *-----------------* + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // 1-----------------* x | 1-----------------2 x + // | + // + // 2 Corner point with a shared face. + { + Relative r1 = { 1.00, 1.00, 1.00 }; + Relative r2 = { 2.00, 2.00, 1.00 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_FALSE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnFace(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnEdge(r1, r2)); + } + + + // + // y | z + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // | | | | | + // 1-----------------2 x | 1-----------------2 x + // | + // + // 2 Corner point with a shared edge. + { + Relative r1 = { 1.00, 1.00, 1.00 }; + Relative r2 = { 2.00, 1.00, 1.00 }; + + EXPECT_TRUE(tools.sameCellProperties(r1, r2)); + + EXPECT_TRUE(GridTools::areCoordOnSameFace(r1, r2)); + EXPECT_TRUE(GridTools::areCoordOnSameEdge(r1, r2)); + EXPECT_FALSE(tools.isSegmentOnFace(r1, r2)); + EXPECT_TRUE(tools.isSegmentOnEdge(r1, r2)); + } } TEST_F(GridToolsTest, uniformDualGrid) { From 6b8b0c3e33ac9667a4016fc6768d84f40aedd601 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Mon, 19 Jan 2026 18:20:20 +0100 Subject: [PATCH 22/35] WIP | DRAFT | Slice without collapsing --- src/core/Slicer.cpp | 25 ++++++++++++++----------- src/core/Slicer.h | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/core/Slicer.cpp b/src/core/Slicer.cpp index db1c4e4..84f0070 100644 --- a/src/core/Slicer.cpp +++ b/src/core/Slicer.cpp @@ -44,42 +44,38 @@ Slicer::Slicer(const Mesh& input, const std::vector& dimensionPol opts_(opts) { // Ensures that all coordinates have a fixed number of decimal places. - Mesh collapsed = input; - collapsed.coordinates = absoluteToRelative(collapsed.coordinates); - collapsed = Collapser{ collapsed, opts_.initialCollapsingDecimalPlaces, dimensionPolicy }.getMesh(); - collapsed.coordinates = relativeToAbsolute(collapsed.coordinates); // Slices. - mesh_.grid = collapsed.grid; + mesh_.grid = input.grid; mesh_.coordinates.reserve(mesh_.coordinates.size() * 100); - mesh_.groups.resize(collapsed.groups.size()); + mesh_.groups.resize(input.groups.size()); for (auto& g : mesh_.groups) { g.elements.reserve(g.elements.size() * 100); } Coordinates& sCoords = mesh_.coordinates; - for (std::size_t g = 0; g < collapsed.groups.size(); g++) { + for (std::size_t g = 0; g < input.groups.size(); g++) { std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::seq, #endif - collapsed.groups[g].elements.begin(), collapsed.groups[g].elements.end(), + input.groups[g].elements.begin(), input.groups[g].elements.end(), [&](auto const& e) { Elements elements; if (e.type == Element::Type::Surface) { - TriV triV{ Geometry::asTriV(e, collapsed.coordinates) }; + TriV triV{ Geometry::asTriV(e, input.coordinates) }; elements = { sliceTriangle(sCoords, triV) }; orient(sCoords, elements, triV); } else if (e.isLine()) { - LinV lineV{ Geometry::asLinV(e, collapsed.coordinates) }; + LinV lineV{ Geometry::asLinV(e, input.coordinates) }; elements = { sliceLine(sCoords, lineV) }; } else if (e.isNode()) { - auto& coordinate = collapsed.coordinates[e.vertices[0]]; + auto& coordinate = input.coordinates[e.vertices[0]]; sCoords.push_back(getRelative(coordinate)); elements = { e }; } @@ -96,10 +92,17 @@ Slicer::Slicer(const Mesh& input, const std::vector& dimensionPol ); } + redundancyCleaner::removeElementsWithCondition(mesh_, [](auto e) {return !(e.isTriangle() || e.isLine() || e.isNode()); }); redundancyCleaner::fuseCoords(mesh_); redundancyCleaner::removeDegenerateElements(mesh_); + double factor = std::pow(10.0, opts_.initialCollapsingDecimalPlaces); + for (auto& coordinate : mesh_.coordinates) { + + coordinate = coordinate.round(factor); + } + // Checks ensured post conditions. meshTools::checkNoCellsAreCrossed(mesh_); meshTools::checkNoNullAreasExist(mesh_); diff --git a/src/core/Slicer.h b/src/core/Slicer.h index e0bae73..1bc51fe 100644 --- a/src/core/Slicer.h +++ b/src/core/Slicer.h @@ -10,7 +10,7 @@ namespace meshlib { namespace core { struct SlicerOptions { - int initialCollapsingDecimalPlaces = 4; + int initialCollapsingDecimalPlaces = 3; }; class Slicer : public utils::GridTools { From cd60b77abafc117caecb9602e3f20edc4a81de3a Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Mon, 19 Jan 2026 18:22:17 +0100 Subject: [PATCH 23/35] WIP | DRAFT | Allow delaunating of non-planar points --- src/core/Delaunator.cpp | 7 ++++--- src/core/Delaunator.h | 2 +- test/core/DelaunatorTest.cpp | 35 ++++++++++++++++++++++++++++------- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/core/Delaunator.cpp b/src/core/Delaunator.cpp index af14d0b..7a3c259 100644 --- a/src/core/Delaunator.cpp +++ b/src/core/Delaunator.cpp @@ -5,7 +5,7 @@ namespace meshlib::core { Delaunator::Delaunator(const Coordinates& globalCoordinates) : globalCoordinates_(&globalCoordinates) {} -Elements Delaunator::mesh(const Polygons& constrainingPolygons) const { +Elements Delaunator::mesh(const Polygons& constrainingPolygons, bool ignoreCoplanarity) const { try { IdSet targetVertices; @@ -13,8 +13,9 @@ Elements Delaunator::mesh(const Polygons& constrainingPolygons) const { targetVertices.insert(polygon.begin(), polygon.end()); } - checkConstraintsArePlanar(targetVertices); - + if (!ignoreCoplanarity) { + checkConstraintsArePlanar(targetVertices); + } PointToId pointsToIds; Triangulation cdt = buildCDT(pointsToIds, targetVertices, constrainingPolygons); diff --git a/src/core/Delaunator.h b/src/core/Delaunator.h index 4393a18..3903e0d 100644 --- a/src/core/Delaunator.h +++ b/src/core/Delaunator.h @@ -17,7 +17,7 @@ class Delaunator { typedef std::vector Polygons; Delaunator(const Coordinates& globalCoordinates); - std::vector mesh(const std::vector& constrainingPolygons = std::vector()) const; + std::vector mesh(const std::vector& constrainingPolygons = std::vector(), bool ignoreCoplanarity = false) const; private: diff --git a/test/core/DelaunatorTest.cpp b/test/core/DelaunatorTest.cpp index d5edbdf..1b3ba1d 100644 --- a/test/core/DelaunatorTest.cpp +++ b/test/core/DelaunatorTest.cpp @@ -10,13 +10,14 @@ class DelaunatorTest : public ::testing::Test { static Coordinates buildCoordinates() { return Coordinates { - Coordinate({0.00, 0.00, 0.00}), - Coordinate({0.00, 1.00, 0.00}), - Coordinate({1.00, 1.00, 0.00}), - Coordinate({1.00, 0.00, 0.00}), - Coordinate({5.00, 5.00, 5.00}), - Coordinate({0.00, 1.00, 0.00}), - Coordinate({0.75, 0.25, 0.00}) + Coordinate({0.00, 0.00, 0.00}), // 0 + Coordinate({0.00, 1.00, 0.00}), // 1 + Coordinate({1.00, 1.00, 0.00}), // 2 + Coordinate({1.00, 0.00, 0.00}), // 3 + Coordinate({5.00, 5.00, 5.00}), // 4 + Coordinate({0.00, 1.00, 0.00}), // 5 + Coordinate({0.75, 0.25, 0.00}), // 6 + Coordinate({1.00, 0.00, 0.10}) // 7 }; } @@ -219,5 +220,25 @@ TEST_F(DelaunatorTest, throw_when_coordinateId_is_out_of_range) EXPECT_ANY_THROW(delaunator.mesh({{ 0, 1, 2, 350 }})); } + EXPECT_THROW(delaunator.mesh({ { 0, 1, 2, 350 } }), std::runtime_error); +} + +TEST_F(DelaunatorTest, mesh_with_unaligned_but_ignore_coplanarity) +{ + auto coords = buildCoordinates(); + Delaunator delaunator(coords); + + Delaunator::Polygons polygons({ {0, 1, 2, 7} }); + + EXPECT_THROW(delaunator.mesh(polygons), std::runtime_error); + + auto tris = delaunator.mesh({ polygons }, true); + + EXPECT_EQ(2, tris.size()); + for (auto const& tri : tris) { + EXPECT_EQ(Element::Type::Surface, tri.type); + EXPECT_EQ(3, tri.vertices.size()); + } +} } \ No newline at end of file From f04191886010a81ceca4c458bd646158ea768f2f Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Mon, 19 Jan 2026 18:24:47 +0100 Subject: [PATCH 24/35] WIP | DRAFT | Calculate getLSFPlaneNormal --- src/utils/Geometry.cpp | 79 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/src/utils/Geometry.cpp b/src/utils/Geometry.cpp index fe2fc4e..e0ad1e6 100644 --- a/src/utils/Geometry.cpp +++ b/src/utils/Geometry.cpp @@ -172,13 +172,86 @@ VecD Geometry::getLSFPlaneNormal(const Coordinates& inPts) { //If you have n data points(x[i], y[i], z[i]), compute the 3x3 symmetric matrix A whose entries are : - //sum_i x[i] * x[i], sum_i x[i] * y[i], sum_i x[i] + // sum_i x[i] * x[i], sum_i x[i] * y[i], sum_i x[i] // sum_i x[i] * y[i], sum_i y[i] * y[i], sum_i y[i] - // sum_i x[i], sum_i y[i], n + // sum_i x[i], sum_i y[i], n // Also compute the 3 element vector b : - + // + // //{sum_i x[i] * z[i], sum_i y[i] * z[i], sum_i z[i]} + + double sumX = 0.0; + double sumY = 0.0; + double sumXX = 0.0; + double sumXY = 0.0; + double sumYY = 0.0; + + double sumZ = 0.0; + double sumXZ = 0.0; + double sumYZ = 0.0; + + for (auto& point : inPts) { + sumX += point[X]; + sumY += point[Y]; + sumXX += point[X] * point[X]; + sumXY += point[X] * point[Y]; + sumYY += point[Y] * point[Y]; + + sumZ += point[Z]; + sumXZ += point[X] * point[Z]; + sumYZ += point[Y] * point[Z]; + } + + std::array, 3> matrix_A({ + std::array({sumXX, sumXY, sumX}), + std::array({sumXY, sumYY, sumY}), + std::array({sumX, sumY, (double) inPts.size()}) + }); + + std::array vector_b({ sumXZ, sumYZ, sumZ }); + + std::array, 3> transpose_A = matrix_A; + + std::array, 3> atA; + + for (std::size_t i = 0; i < 3; ++i) { + for (std::size_t j = 0; j < 3; ++j) { + double sum = matrix_A[i][0] * transpose_A[0][j] + matrix_A[i][1] * transpose_A[1][j] + matrix_A[i][2] * transpose_A[2][j]; + atA[i][j] = sum; + } + } + std::array atb; + + for (std::size_t i = 0; i < 3; ++i) { + double sum = transpose_A[i][0] * vector_b[0] + transpose_A[i][1] * vector_b[1] + transpose_A[i][2] * vector_b[2]; + atb[i] = sum; + } + + + // [AtA00, AtA01, AtA02] [Atb0] + // [AtA10, AtA11, AtA12] * x = [Atb1] + // [AtA20, AtA21, AtA22] [Atb2] + + // [AtA00, AtA01, AtA02] [x] [Atb0] + // [AtA10, AtA11, AtA12] * [y] = [Atb1] + // [AtA20, AtA21, AtA22] [z] [Atb2] + // + // AtA00 * x + AtA01 * y + AtA02 * z = Atb0 + // AtA10 * x + AtA11 * y + AtA12 * z = Atb1 + // AtA20 * x + AtA21 * y + AtA22 * z = Atb2 + // + // + // ( + // + // + // + // + // + + + + throw std::runtime_error("Not implemented"); VecD res({ 0.0, 0.0, 0.0 }); return res / res.norm(); From d02db6dc411b8485ee2ee629793442f9d1896071 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Thu, 22 Jan 2026 14:02:07 +0100 Subject: [PATCH 25/35] GridTools | Make CellElem map that only maps elements to one cell --- src/utils/GridTools.cpp | 25 ++++++++++++++++++++++++- src/utils/GridTools.h | 3 +++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/utils/GridTools.cpp b/src/utils/GridTools.cpp index 19b27b2..303e86d 100644 --- a/src/utils/GridTools.cpp +++ b/src/utils/GridTools.cpp @@ -830,7 +830,7 @@ std::map> GridTools::buildCellElemMap( { std::map> cells; for (auto e = elems.begin(); e != elems.end(); ++e) { - + Coordinate centroid; for (std::size_t i = 0; i < e->vertices.size(); i++) { centroid += coords[e->vertices[i]] / double(e->vertices.size()); @@ -844,6 +844,29 @@ std::map> GridTools::buildCellElemMap( return cells; } +std::map> GridTools::buildCellElemMapStrict( + const std::vector& elems, + const Relatives& coords) const +{ + std::map> cells; + for (auto e = elems.begin(); e != elems.end(); ++e) { + + Relative centroid; + for (std::size_t i = 0; i < e->vertices.size(); i++) { + centroid += coords[e->vertices[i]] / double(e->vertices.size()); + } + Cell local = toCell(centroid); + for (Axis axis = X; axis <= Z; ++axis) { + if (local(axis) == numCellsDir(axis)) { + local(axis)--; + } + } + + cells[local].push_back(&(*e)); + } + return cells; +} + std::map> GridTools::buildCellTriMap( const Elements& elems, const Coordinates& coords) const diff --git a/src/utils/GridTools.h b/src/utils/GridTools.h index 35e4d9f..fa4e50d 100644 --- a/src/utils/GridTools.h +++ b/src/utils/GridTools.h @@ -97,6 +97,9 @@ class GridTools { std::map> buildCellElemMap( const std::vector& elems, const std::vector& coords) const; + std::map> buildCellElemMapStrict( + const std::vector& elems, + const Relatives& coords) const; std::map> buildCellCoordMap( std::vector& coords) const; std::map> buildCellTriMap( From 131a67860de0de15c2dc2f1e2150da9407a89cd0 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Fri, 23 Jan 2026 15:55:49 +0100 Subject: [PATCH 26/35] get LSFPlane Normal --- src/cgal/CMakeLists.txt | 2 +- .../{Delaunator.cpp => DelaunatorCGAL.cpp} | 38 ++++++------ src/cgal/{Delaunator.h => DelaunatorCGAL.h} | 4 +- src/core/Delaunator.cpp | 25 ++++---- test/CMakeLists.txt | 2 +- ...aunatorTest.cpp => DelaunatorCGALTest.cpp} | 60 +++++++++---------- test/core/DelaunatorTest.cpp | 21 ++++++- 7 files changed, 87 insertions(+), 65 deletions(-) rename src/cgal/{Delaunator.cpp => DelaunatorCGAL.cpp} (81%) rename src/cgal/{Delaunator.h => DelaunatorCGAL.h} (95%) rename test/cgal/{DelaunatorTest.cpp => DelaunatorCGALTest.cpp} (76%) diff --git a/src/cgal/CMakeLists.txt b/src/cgal/CMakeLists.txt index 79798fd..4b9068d 100644 --- a/src/cgal/CMakeLists.txt +++ b/src/cgal/CMakeLists.txt @@ -1,7 +1,7 @@ message(STATUS "Creating build system for tessellator-cgal") add_library(tessellator-cgal - "Delaunator.cpp" + "DelaunatorCGAL.cpp" "HPolygonSet.cpp" "Manifolder.cpp" "PolyhedronTools.cpp" diff --git a/src/cgal/Delaunator.cpp b/src/cgal/DelaunatorCGAL.cpp similarity index 81% rename from src/cgal/Delaunator.cpp rename to src/cgal/DelaunatorCGAL.cpp index f3ec2ea..3d19740 100644 --- a/src/cgal/Delaunator.cpp +++ b/src/cgal/DelaunatorCGAL.cpp @@ -1,4 +1,4 @@ -#include "Delaunator.h" +#include "DelaunatorCGAL.h" #include "utils/Geometry.h" #include "LSFPlane.h" @@ -6,9 +6,9 @@ namespace meshlib::cgal { using namespace utils; -const double Delaunator::COPLANARITY_ANGLE_TOLERANCE = 0.1; +const double DelaunatorCGAL::COPLANARITY_ANGLE_TOLERANCE = 0.1; -Delaunator::Delaunator(const Coordinates* global, const ElementsView& elements) +DelaunatorCGAL::DelaunatorCGAL(const Coordinates* global, const ElementsView& elements) { if (global == nullptr) { throw std::runtime_error("Global list of coordinates must be defined"); @@ -20,18 +20,18 @@ Delaunator::Delaunator(const Coordinates* global, const ElementsView& elements) } } -std::vector Delaunator::mesh( +std::vector DelaunatorCGAL::mesh( const IdSet& inIds, const std::vector& constrainingPolygons) const { auto ids = filterIdsByConstraints(inIds, constrainingPolygons); checkIdsAreInRange(ids, constrainingPolygons); const IndexPointToId pointToId = buildPointsInIndex(ids, constrainingPolygons); - CDT cdt = buildCDT(pointToId, ids, constrainingPolygons); + Triangulation cdt = buildCDT(pointToId, ids, constrainingPolygons); return convertFromCDT(cdt, pointToId); } -void Delaunator::checkIdsAreInRange( +void DelaunatorCGAL::checkIdsAreInRange( const IdSet& inIds, const std::vector& constrainingPolygons) const { @@ -53,8 +53,8 @@ void Delaunator::checkIdsAreInRange( } } -std::vector Delaunator::convertFromCDT( - const CDT& cdt, const IndexPointToId& pointToId) const +std::vector DelaunatorCGAL::convertFromCDT( + const Triangulation& cdt, const IndexPointToId& pointToId) const { for (auto v : cdt.finite_vertex_handles()) { if (pointToId.left.count(v->point()) == 0) { @@ -79,12 +79,12 @@ std::vector Delaunator::convertFromCDT( return res; } -Delaunator::CDT Delaunator::buildCDT( +DelaunatorCGAL::Triangulation DelaunatorCGAL::buildCDT( const IndexPointToId& pointToId, const IdSet& inIds, const Polygons& constrainingPolygons) { - CDT cdt; + Triangulation cdt; for (auto const& polygon : constrainingPolygons) { Polygon_2 cgalPoly; for (auto const& id : polygon) { @@ -101,7 +101,7 @@ Delaunator::CDT Delaunator::buildCDT( return cdt; } -void Delaunator::mark_domains(CDT& ct, Face_handle start, int index, std::list& border) +void DelaunatorCGAL::mark_domains(Triangulation& ct, Face_handle start, int index, std::list& border) { if (start->info().nesting_level != -1) { return; @@ -114,7 +114,7 @@ void Delaunator::mark_domains(CDT& ct, Face_handle start, int index, std::listinfo().nesting_level == -1) { fh->info().nesting_level = index; for (int i = 0; i < 3; i++) { - CDT::Edge e(fh, i); + Triangulation::Edge e(fh, i); Face_handle n = fh->neighbor(i); if (n->info().nesting_level == -1) { if (ct.is_constrained(e)) border.push_back(e); @@ -125,7 +125,7 @@ void Delaunator::mark_domains(CDT& ct, Face_handle start, int index, std::listinfo().nesting_level = -1; } - std::list border; + std::list border; mark_domains(cdt, cdt.infinite_face(), 0, border); while (!border.empty()) { - CDT::Edge e = border.front(); + Triangulation::Edge e = border.front(); border.pop_front(); Face_handle n = e.first->neighbor(e.second); if (n->info().nesting_level == -1) { @@ -155,7 +155,7 @@ void Delaunator::mark_domains(CDT& cdt) } } -Delaunator::IndexPointToId Delaunator::buildPointsInIndex( +DelaunatorCGAL::IndexPointToId DelaunatorCGAL::buildPointsInIndex( const IdSet& inIds, const Polygons& constrainingPolygons) const { @@ -189,7 +189,7 @@ Delaunator::IndexPointToId Delaunator::buildPointsInIndex( IndexPointToId res; for (std::size_t i = 0; i < cs.size(); i++) { res.insert( IndexPointToId::value_type( - CDT::Point(cs[i](0), cs[i](1)), + Triangulation::Point(cs[i](0), cs[i](1)), originalIds[i]) ); } diff --git a/src/cgal/Delaunator.h b/src/cgal/DelaunatorCGAL.h similarity index 95% rename from src/cgal/Delaunator.h rename to src/cgal/DelaunatorCGAL.h index 2e283b9..ce51934 100644 --- a/src/cgal/Delaunator.h +++ b/src/cgal/DelaunatorCGAL.h @@ -14,12 +14,12 @@ namespace meshlib { namespace cgal { -class Delaunator { +class DelaunatorCGAL { public: typedef std::vector Polygon; typedef std::vector Polygons; - Delaunator(const Coordinates* globalCoordinates, const ElementsView& elems = ElementsView()); + DelaunatorCGAL(const Coordinates* globalCoordinates, const ElementsView& elems = ElementsView()); std::vector mesh( const IdSet& vertexIds, diff --git a/src/core/Delaunator.cpp b/src/core/Delaunator.cpp index 7a3c259..8f7f54a 100644 --- a/src/core/Delaunator.cpp +++ b/src/core/Delaunator.cpp @@ -1,5 +1,6 @@ #include "Delaunator.h" #include "utils\Geometry.h" +#include "cgal\LSFPlane.h" namespace meshlib::core { @@ -55,24 +56,28 @@ Delaunator::Triangulation Delaunator::buildCDT( originalIds.push_back(id); } - VecD normal({ 0.,0.,0. }); + VecD normal = cgal::LSFPlane(rotatedCoordinates.begin(), rotatedCoordinates.end()).getNormal(); - for (std::size_t i = 0; i + 2 < rotatedCoordinates.size() && normal.norm() == 0.0; ++i) { - for (std::size_t j = i + 1; j + 1 < rotatedCoordinates.size() && normal.norm() == 0.0; ++j) { - for (std::size_t k = j + 1; k < rotatedCoordinates.size() && normal.norm() == 0.0; ++k) { - normal = utils::Geometry::normal(TriV({ rotatedCoordinates[i], rotatedCoordinates[j], rotatedCoordinates[k] })); - } - } - } - utils::Geometry::rotateToXYPlane(rotatedCoordinates.begin(), rotatedCoordinates.end(), normal); + std::size_t planarId = 0; for (std::size_t index = 0; index < targetVertices.size(); ++index) { Point point({ rotatedCoordinates[index][X], rotatedCoordinates[index][Y] }); - PointId pointId(index); + PointId pointId(planarId); + /* + if (pointsToIds.left.find(point) == pointsToIds.left.end()) { + pointsToIds.insert(PointToId::value_type(point, originalIds[index])); + ++planarId; + } + else { + originalIds[index] = pointsToIds.left.at(point); + } + /**/ + pointsToIds.insert(PointToId::value_type(point, originalIds[index])); + } std::vector newVertices; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3553654..ab5a6b4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -52,7 +52,7 @@ if (TESSELLATOR_ENABLE_CGAL) target_sources(tessellator_tests PRIVATE "cgal/ArrangmentTest.cpp" - "cgal/DelaunatorTest.cpp" + "cgal/DelaunatorCGALTest.cpp" "cgal/HPolygonSetTest.cpp" "cgal/ManifolderTest.cpp" "cgal/PolyhedronToolsTest.cpp" diff --git a/test/cgal/DelaunatorTest.cpp b/test/cgal/DelaunatorCGALTest.cpp similarity index 76% rename from test/cgal/DelaunatorTest.cpp rename to test/cgal/DelaunatorCGALTest.cpp index b66963e..6e12fd5 100644 --- a/test/cgal/DelaunatorTest.cpp +++ b/test/cgal/DelaunatorCGALTest.cpp @@ -1,11 +1,11 @@ #include "gtest/gtest.h" -#include "cgal/Delaunator.h" +#include "cgal/DelaunatorCGAL.h" #include "utils/Types.h" namespace meshlib::cgal { -class DelaunatorTest : public ::testing::Test { +class DelaunatorCGALTest : public ::testing::Test { public: static Coordinates buildCoordinates() { @@ -34,10 +34,10 @@ class DelaunatorTest : public ::testing::Test { } }; -TEST_F(DelaunatorTest, mesh_one_triangle_with_constraining_polygon) +TEST_F(DelaunatorCGALTest, mesh_one_triangle_with_constraining_polygon) { auto coords = buildCoordinates(); - Delaunator delaunator(&coords); + DelaunatorCGAL delaunator(&coords); auto tris = delaunator.mesh({}, { {0, 1, 2} }); @@ -49,7 +49,7 @@ TEST_F(DelaunatorTest, mesh_one_triangle_with_constraining_polygon) } -TEST_F(DelaunatorTest, mesh_bowtie) +TEST_F(DelaunatorCGALTest, mesh_bowtie) { std::vector coords(5); coords [0] = Coordinate{{0.0, 0.0 , 0.0 }}; @@ -59,9 +59,9 @@ TEST_F(DelaunatorTest, mesh_bowtie) coords [4] = Coordinate{{1.0, 0.0 , 0.0 }}; - Delaunator delaunator(&coords); + DelaunatorCGAL delaunator(&coords); IdSet cIds = { 0,1,2,3,4,2 }; - Delaunator::Polygon boundary = { 0,1,2,3,4,2 }; + DelaunatorCGAL::Polygon boundary = { 0,1,2,3,4,2 }; auto tris = delaunator.mesh({}, { boundary }); EXPECT_EQ(2, tris.size()); @@ -70,7 +70,7 @@ TEST_F(DelaunatorTest, mesh_bowtie) EXPECT_EQ(3, tri.vertices.size()); } } -TEST_F(DelaunatorTest, mesh_bowtie_2) +TEST_F(DelaunatorCGALTest, mesh_bowtie_2) { std::vector coords(6); coords [0] = Coordinate{{0.0, 0.0, 0.0 }}; @@ -81,8 +81,8 @@ TEST_F(DelaunatorTest, mesh_bowtie_2) coords [5] = Coordinate{{0.2, 0.2, 0.0 }}; - Delaunator delaunator(&coords); - Delaunator::Polygon boundary = { 0,5,4,1,2,4 }; + DelaunatorCGAL delaunator(&coords); + DelaunatorCGAL::Polygon boundary = { 0,5,4,1,2,4 }; auto tris = delaunator.mesh({}, { boundary }); EXPECT_EQ(2, tris.size()); @@ -92,7 +92,7 @@ TEST_F(DelaunatorTest, mesh_bowtie_2) } } -TEST_F(DelaunatorTest, mesh_tri_with_hole) +TEST_F(DelaunatorCGALTest, mesh_tri_with_hole) { std::vector coords(6); coords[0] = Coordinate{ {0.0, 0.0, 0.0 } }; @@ -103,9 +103,9 @@ TEST_F(DelaunatorTest, mesh_tri_with_hole) coords[5] = Coordinate{ {0.15, 0.2, 0.0 } }; - Delaunator delaunator(&coords); - Delaunator::Polygon outer_boundary = { 0,1,2}; - Delaunator::Polygon inner_boundary = { 3,4,5 }; + DelaunatorCGAL delaunator(&coords); + DelaunatorCGAL::Polygon outer_boundary = { 0,1,2}; + DelaunatorCGAL::Polygon inner_boundary = { 3,4,5 }; auto tris = delaunator.mesh({}, { outer_boundary, inner_boundary }); EXPECT_EQ(6, tris.size()); @@ -115,12 +115,12 @@ TEST_F(DelaunatorTest, mesh_tri_with_hole) } } -TEST_F(DelaunatorTest, mesh_one_triangle_other_call) +TEST_F(DelaunatorCGALTest, mesh_one_triangle_other_call) { auto coords = buildCoordinates(); - Delaunator delaunator(&coords); + DelaunatorCGAL delaunator(&coords); IdSet cIds = { 0,1,2 }; - Delaunator::Polygon boundary = { 0,1,2 }; + DelaunatorCGAL::Polygon boundary = { 0,1,2 }; auto tris = delaunator.mesh(cIds, {boundary}); EXPECT_EQ(1, tris.size()); @@ -130,7 +130,7 @@ TEST_F(DelaunatorTest, mesh_one_triangle_other_call) } } -TEST_F(DelaunatorTest, mesh_with_invalid_constraining_polygon) +TEST_F(DelaunatorCGALTest, mesh_with_invalid_constraining_polygon) { std::vector coords(5); coords[0] = Coordinate{ {0.25, 0.00, 0.00} }; @@ -139,7 +139,7 @@ TEST_F(DelaunatorTest, mesh_with_invalid_constraining_polygon) coords[3] = Coordinate{ {0.25, 0.00, 1.00} }; coords[4] = Coordinate{ {1.00, 0.00, 1.00} }; - Delaunator delaunator(&coords); + DelaunatorCGAL delaunator(&coords); { std::vector constrainingPolygon = { 0,1,2,3,4 }; EXPECT_ANY_THROW(auto tris = delaunator.mesh({}, { constrainingPolygon })); @@ -157,10 +157,10 @@ TEST_F(DelaunatorTest, mesh_with_invalid_constraining_polygon) } } } -TEST_F(DelaunatorTest, mesh_two_triangles) +TEST_F(DelaunatorCGALTest, mesh_two_triangles) { auto coords = buildCoordinates(); - Delaunator delaunator(&coords); + DelaunatorCGAL delaunator(&coords); auto tris = delaunator.mesh({}, { {0, 1, 2, 3} }); @@ -171,10 +171,10 @@ TEST_F(DelaunatorTest, mesh_two_triangles) } } -TEST_F(DelaunatorTest, mesh_example_path) +TEST_F(DelaunatorCGALTest, mesh_example_path) { auto coords = buildPathCoordinates(); - Delaunator delaunator(&coords); + DelaunatorCGAL delaunator(&coords); auto tris = delaunator.mesh({}, { { 0, 1, 4, 5, 2, 3 } }); @@ -185,7 +185,7 @@ TEST_F(DelaunatorTest, mesh_example_path) } } -TEST_F(DelaunatorTest, mesh_polygons_cw_or_ccw) { +TEST_F(DelaunatorCGALTest, mesh_polygons_cw_or_ccw) { Coordinates c{ Coordinate({0.0, 0.0, 0.0}), Coordinate({0.0, 1.0, 0.0}), @@ -195,8 +195,8 @@ TEST_F(DelaunatorTest, mesh_polygons_cw_or_ccw) { Coordinate({0.8, 0.9, 0.0}) }; - Delaunator delaunator(&c); - Delaunator::Polygon p1 = { 0, 1, 2 }; + DelaunatorCGAL delaunator(&c); + DelaunatorCGAL::Polygon p1 = { 0, 1, 2 }; auto trisCW = delaunator.mesh({}, { p1, { 3, 4, 5 } }); auto trisCCW = delaunator.mesh({}, { p1, { 5, 4, 3 } }); @@ -204,18 +204,18 @@ TEST_F(DelaunatorTest, mesh_polygons_cw_or_ccw) { EXPECT_EQ(trisCW.size(), trisCCW.size()); } -TEST_F(DelaunatorTest, when_not_aligned) +TEST_F(DelaunatorCGALTest, when_not_aligned) { auto coords = buildCoordinates(); - Delaunator delaunator(&coords); + DelaunatorCGAL delaunator(&coords); EXPECT_NO_THROW(delaunator.mesh({ 0, 1, 2, 4 })); } -TEST_F(DelaunatorTest, throw_when_coordinateId_is_out_of_range) +TEST_F(DelaunatorCGALTest, throw_when_coordinateId_is_out_of_range) { auto coords = buildCoordinates(); - Delaunator delaunator(&coords); + DelaunatorCGAL delaunator(&coords); EXPECT_ANY_THROW(delaunator.mesh({ 0, 1, 2, 350 })); } diff --git a/test/core/DelaunatorTest.cpp b/test/core/DelaunatorTest.cpp index 1b3ba1d..5943cbf 100644 --- a/test/core/DelaunatorTest.cpp +++ b/test/core/DelaunatorTest.cpp @@ -218,8 +218,6 @@ TEST_F(DelaunatorTest, throw_when_coordinateId_is_out_of_range) auto coords = buildCoordinates(); Delaunator delaunator(coords); - EXPECT_ANY_THROW(delaunator.mesh({{ 0, 1, 2, 350 }})); -} EXPECT_THROW(delaunator.mesh({ { 0, 1, 2, 350 } }), std::runtime_error); } @@ -240,5 +238,24 @@ TEST_F(DelaunatorTest, mesh_with_unaligned_but_ignore_coplanarity) EXPECT_EQ(3, tri.vertices.size()); } } +/* +TEST_F(DelaunatorTest, mesh_with_unaligned_but_ignore_coplanarity_2) +{ + auto coords = buildCoordinates(); + Delaunator delaunator(coords); + + Delaunator::Polygons polygons({ {0, 1, 2, 3, 7} }); + + EXPECT_THROW(delaunator.mesh(polygons), std::runtime_error); + + auto tris = delaunator.mesh({ polygons }, true); + + EXPECT_EQ(2, tris.size()); + for (auto const& tri : tris) { + EXPECT_EQ(Element::Type::Surface, tri.type); + EXPECT_EQ(3, tri.vertices.size()); + } +} +/**/ } \ No newline at end of file From a07e1866528c498a8908e00bfa4a6148acb64489 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Fri, 23 Jan 2026 15:56:35 +0100 Subject: [PATCH 27/35] WIP | DRAFT | Update edge calculation to include degenerate elements --- src/utils/ElemGraph.cpp | 53 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/src/utils/ElemGraph.cpp b/src/utils/ElemGraph.cpp index 7ac84ed..04368fd 100644 --- a/src/utils/ElemGraph.cpp +++ b/src/utils/ElemGraph.cpp @@ -251,15 +251,58 @@ std::vector buildTrianglesAdjacenciesList(const ElementsView& es) void ElemGraph::constructEdgesFromTriangles(const ElementsView& elems, const Coordinates& coords) { + std::map degenerateTriangleAdjacent; + for (auto const& adj: buildTrianglesAdjacenciesList(elems)) { - - ElementId eId1 = adj.first; ElementId eId2 = adj.second; const double pi = atan(1) * 4.0; - double angle = Geometry::normal(Geometry::asTriV(*elems[eId1], coords)) - .angle(Geometry::normal(Geometry::asTriV(*elems[eId2], coords))); - this->addEdge(eId1, eId2, angle * 360.0 / (2.0 * pi)); + + auto triV1 = Geometry::asTriV(*elems[eId1], coords); + auto triV2 = Geometry::asTriV(*elems[eId2], coords); + auto vector1 = Geometry::normal(triV1); + auto vector2 = Geometry::normal(triV2); + + auto furthestAdjacentVector1 = vector1; + auto furthestAdjacentVector2 = vector2; + + if (vector1.norm() == 0.0) { + ElementId furthestAdjacentId = eId1; + + while (degenerateTriangleAdjacent.find(furthestAdjacentId) != degenerateTriangleAdjacent.end()) { + furthestAdjacentId = degenerateTriangleAdjacent[furthestAdjacentId]; + } + + furthestAdjacentVector1 = Geometry::normal(Geometry::asTriV(*elems[furthestAdjacentId], coords)); + } + + if (vector2.norm() == 0.0) { + ElementId furthestAdjacentId = eId2; + + while (degenerateTriangleAdjacent.find(furthestAdjacentId) != degenerateTriangleAdjacent.end()) { + furthestAdjacentId = degenerateTriangleAdjacent[furthestAdjacentId]; + } + + furthestAdjacentVector2 = Geometry::normal(Geometry::asTriV(*elems[furthestAdjacentId], coords)); + } + + double angle; + + if (furthestAdjacentVector1.norm() == 0.0 || furthestAdjacentVector2.norm() == 0) { + angle = 0; + } + else { + angle = furthestAdjacentVector1.angleDeg(furthestAdjacentVector2); + } + + if (vector1.norm() == 0.0 && degenerateTriangleAdjacent.find(eId1) == degenerateTriangleAdjacent.end()) { + degenerateTriangleAdjacent[eId1] = eId2; + } + else if (vector2.norm() == 0.0){ + degenerateTriangleAdjacent[eId2] = eId1; + } + + this->addEdge(eId1, eId2, angle); } } From e09e398592b46b8068e05690366239afa0f2f366 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Fri, 23 Jan 2026 15:57:13 +0100 Subject: [PATCH 28/35] WIP | DRAFT | debugging code --- src/core/Collapser.cpp | 38 ++++++ src/core/Slicer.cpp | 27 ++++- src/core/Smoother.cpp | 82 +++++++++++-- src/core/SmootherTools.cpp | 167 +++++++++++++++------------ src/meshers/ConformalMesher.cpp | 81 ++++++++++++- src/utils/Geometry.cpp | 35 +++++- src/utils/Geometry.h | 10 +- src/utils/MeshTools.cpp | 4 + src/utils/RedundancyCleaner.cpp | 4 + test/core/SlicerTest.cpp | 134 ++++----------------- test/core/SmootherTest.cpp | 6 +- test/core/StaircaserTest.cpp | 113 +++++++++++++++++- test/meshers/ConformalMesherTest.cpp | 16 ++- 13 files changed, 497 insertions(+), 220 deletions(-) diff --git a/src/core/Collapser.cpp b/src/core/Collapser.cpp index 398f0ff..4fb8a73 100644 --- a/src/core/Collapser.cpp +++ b/src/core/Collapser.cpp @@ -3,6 +3,8 @@ #include "utils/Geometry.h" #include "utils/RedundancyCleaner.h" #include "utils/MeshTools.h" +#include "app/vtkIO.h" +#include "utils/GridTools.h" #include "Collapser.h" @@ -13,6 +15,12 @@ using namespace utils; Collapser::Collapser(const Mesh& in, int decimalPlaces, const std::vector& dimensionPolicy) { + Grid auxGrid; + + auxGrid[X] = utils::GridTools::linspace(0.0, in.grid[X].size() - 1, in.grid[X].size()); + auxGrid[Y] = utils::GridTools::linspace(0.0, in.grid[Y].size() - 1, in.grid[Y].size()); + auxGrid[Z] = utils::GridTools::linspace(0.0, in.grid[Z].size() - 1, in.grid[Z].size()); + if (dimensionPolicy.size() == 0) { dimensionPolicy_ = std::vector(in.groups.size(), Element::Type::Surface); } @@ -21,6 +29,9 @@ Collapser::Collapser(const Mesh& in, int decimalPlaces, const std::vector& v = element.vertices; + /* + bool relevantTriangleFound = true; + for (auto& coordId : v) { + if (coords[v[X]] < 1.0 && coords[v[Y]] > 2.0 && coords[v[Y]] < 3.0) { + relevantTriangleFound = false; + } + } + */ std::pair replace; std::array sumOfDistances{ 0,0,0 }; @@ -75,12 +102,23 @@ void Collapser::collapseDegenerateElements(Mesh& mesh, const double& areaThresho else { coords[element.vertices[midId]] = coords[element.vertices[(midId + 2) % 3]]; } + /* + for (auto& coordId : v) { + if (coords[v[X]] < 1.0 && coords[v[Y]] > 2.0 && coords[v[Y]] < 3.0) { + bool noop = true; + } + } + */ } } redundancyCleaner::fuseCoords(mesh); redundancyCleaner::cleanCoords(mesh); + std::string path = "testData/cases/alhambra/alhambra.collapsingStep" + std::to_string(iter) + ".vtk"; + + vtkIO::exportMeshToVTU(path, mesh_); + for (auto & group : mesh.groups) { for (auto& element : group.elements) { if (element.isLine()){ diff --git a/src/core/Slicer.cpp b/src/core/Slicer.cpp index 84f0070..1a86653 100644 --- a/src/core/Slicer.cpp +++ b/src/core/Slicer.cpp @@ -5,6 +5,7 @@ #include "utils/MeshTools.h" #include "utils/ConvexHull.h" #include "core/Collapser.h" +#include "app/vtkIO.h" #ifdef TESSELLATOR_EXECUTION_POLICIES #include @@ -97,15 +98,28 @@ Slicer::Slicer(const Mesh& input, const std::vector& dimensionPol redundancyCleaner::fuseCoords(mesh_); redundancyCleaner::removeDegenerateElements(mesh_); + + // vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.sliced-pre-collapse.vtk", mesh_); + double factor = std::pow(10.0, opts_.initialCollapsingDecimalPlaces); for (auto& coordinate : mesh_.coordinates) { coordinate = coordinate.round(factor); } + /** + Mesh collapsed = mesh_; + collapsed = Collapser{ collapsed, opts_.initialCollapsingDecimalPlaces, dimensionPolicy }.getMesh(); + + vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.collapsed.vtk", collapsed); + // Checks ensured post conditions. - meshTools::checkNoCellsAreCrossed(mesh_); - meshTools::checkNoNullAreasExist(mesh_); + meshTools::checkNoCellsAreCrossed(collapsed); + meshTools::checkNoNullAreasExist(collapsed); + /**/ + + redundancyCleaner::fuseCoords(mesh_); + redundancyCleaner::removeDegenerateElements(mesh_); } Elements Slicer::sliceTriangle( @@ -131,6 +145,13 @@ Elements Slicer::sliceTriangle( auto n{ utils::Geometry::normal(tri) }; auto path = utils::ConvexHull(&sCoords).get(vIds, n); Elements newTris = buildTrianglesFromPath(sCoords, path); + // Check if coordinates coincide in path. + for (auto& tri : newTris) { + if (elementCrossesGrid(tri, sCoords)) { + throw std::runtime_error("Triangle crosses grid"); + } + } + res.insert(res.end(), newTris.begin(), newTris.end()); } return res; @@ -252,7 +273,7 @@ Elements Slicer::buildTrianglesFromPath( if (path.size() < 3) { return tris; } - + auto p{ path }; { std::size_t turns = 0; diff --git a/src/core/Smoother.cpp b/src/core/Smoother.cpp index 9994d2d..23ebf67 100644 --- a/src/core/Smoother.cpp +++ b/src/core/Smoother.cpp @@ -26,9 +26,17 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : meshTools::checkNoCellsAreCrossed(mesh); mesh_ = mesh; + + redundancyCleaner::fuseCoords(mesh_); + + /* mesh_ = meshTools::duplicateCoordinatesUsedByDifferentGroups(mesh_); + + /* + mesh_ = meshTools::duplicateCoordinatesSharedBySingleTrianglesVertex(mesh_); - + */ + Mesh res = mesh_; SmootherTools sT_(res.grid, res.coordinates); for (auto& g : res.groups) { @@ -36,22 +44,23 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : sT_.buildSingularIds(g.elements, mesh_.coordinates, opts_.featureDetectionAngle); std::vector patchs; - for (auto const& cell : sT_.buildCellElemMap(g.elements, mesh_.coordinates)) { + for (auto const& cell : sT_.buildCellElemMapStrict(g.elements, mesh_.coordinates)) { + bool noop = false; for (auto const& p : Geometry::buildDisjointSmoothSets(cell.second, mesh_.coordinates, opts_.featureDetectionAngle)) { patchs.push_back(p); } } - /**/ + /* std::for_each(patchs.begin(), patchs.end(), [&](auto& p) { sT_.remeshBoundary(g.elements, res.coordinates, mesh_.coordinates, p); }); - /**/ + /* std::for_each(patchs.begin(), patchs.end(), [&](auto& p) { sT_.collapsePointsOnCellEdges(res.coordinates, p, singularIds, opts_.contourAlignmentAngle); }); - /**/ + /* std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, @@ -59,7 +68,7 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : patchs.begin(), patchs.end(), [&](auto& p) { sT_.collapsePointsOnCellFaces(res.coordinates, p, singularIds); }); - /**/ + /* std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, @@ -67,7 +76,7 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : patchs.begin(), patchs.end(), [&](auto& p) { sT_.collapsePointsOnFeatureEdges(res.coordinates, p, singularIds); }); - /**/ + /* std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, @@ -75,19 +84,74 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : patchs.begin(), patchs.end(), [&](auto& p) { sT_.collapseInteriorPointsToBound(res.coordinates, p); }); - /**/ + /* + + } + + redundancyCleaner::fuseCoords(res); + redundancyCleaner::removeDegenerateElements(res); + res = buildMeshFilteringElements(res, isTriangle); + /* + for (auto& g : res.groups) { + auto const singularIds = + sT_.buildSingularIds(g.elements, mesh_.coordinates, opts_.featureDetectionAngle); + std::vector patchs; + for (auto const& cell : sT_.buildCellElemMap(g.elements, mesh_.coordinates)) { + for (auto const& p : + Geometry::buildDisjointSmoothSets(cell.second, mesh_.coordinates, opts_.featureDetectionAngle)) { + patchs.push_back(p); + } + } + /**/ + int counter = 0; std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, #endif patchs.begin(), patchs.end(), [&](auto& p) { + sT_.collapsePointsOnContourWithDelanautor(g.elements, mesh_.coordinates, p, singularIds); + ++counter; + }); + + counter += 1; + /* + std::for_each( +#ifdef TESSELLATOR_EXECUTION_POLICIES + std::execution::par, +#endif + patchs.begin() + 80, patchs.begin() + 88, [&](auto& p) { sT_.collapsePointsOnContourWithDelanautor(g.elements, res.coordinates, p, singularIds); + ++counter; + }); + + counter += 1; + + /* + + std::for_each( +#ifdef TESSELLATOR_EXECUTION_POLICIES + std::execution::par, +#endif + patchs.begin() + 44, patchs.begin() + 46, [&](auto& p) { + sT_.collapsePointsOnContourWithDelanautor(g.elements, res.coordinates, p, singularIds); + ++counter; + }); + + counter += 1; + /* + std::for_each( +#ifdef TESSELLATOR_EXECUTION_POLICIES + std::execution::par, +#endif + patchs.begin() + 47, patchs.end(), [&](auto& p) { + sT_.collapsePointsOnContourWithDelanautor(g.elements, res.coordinates, p, singularIds); + ++counter; }); /**/ } - + /**/ redundancyCleaner::fuseCoords(res); redundancyCleaner::removeDegenerateElements(res); res = buildMeshFilteringElements(res, isTriangle); diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index 4bc46a8..e3986c8 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -151,7 +151,16 @@ SmootherTools::SingularIds SmootherTools::buildSingularIds( const std::size_t i = &g - &graphs.front(); for (std::size_t j = i + 1; j < graphs.size(); j++) { auto edge = g.intersect(graphs[j]).getVertices(); + for (auto const id : edge) { + const Coordinate& coordinate = coords[id]; + /* + if (!isRelativeInterior(coordinate)) { + const Coordinate& coordinate1 = coords[*edge.begin()]; + const Coordinate& coordinate2 = coords[*edge.rbegin()]; + bool noop = true; + } + */ if (featureIds.count(id)) { cornerIds.insert(id); } @@ -161,7 +170,7 @@ SmootherTools::SingularIds SmootherTools::buildSingularIds( } } - return SingularIds(featureIds, contourIds, cornerIds); + return SingularIds(featureIds, contourIds, cornerIds); // Only thing missing are the internal corners. } @@ -171,8 +180,22 @@ void SmootherTools::collapsePointsOnCellEdges( const SingularIds& singularIds, double alignmentAngle) { + IdSet commonFeatureIds; + IdSet commonContourIds; + IdSet commonEdgeIds; { IdSet vertices = CoordGraph(patch).getVertices(); + for (auto vertexId : vertices) { + if (std::find(singularIds.featureIds().begin(), singularIds.featureIds().end(), vertexId) != singularIds.featureIds().end()) { + commonFeatureIds.insert(vertexId); + } + if (std::find(singularIds.contourIds().begin(), singularIds.contourIds().end(), vertexId) != singularIds.contourIds().end()) { + commonContourIds.insert(vertexId); + } + if (std::find(singularIds.edgeIds().begin(), singularIds.edgeIds().end(), vertexId) != singularIds.edgeIds().end()) { + commonEdgeIds.insert(vertexId); + } + } if (!std::all_of( vertices.begin(), vertices.end(), @@ -295,7 +318,9 @@ bool SmootherTools::patchIsPlanar( boundCs.push_back(cs[id]); } - return Geometry::areCoordinatesCoplanar(boundCs.begin(), boundCs.end()); + return Geometry::areCoordinatesCoplanar(boundCs.begin(), boundCs.end() + //, 1.e-4/2); + ); } void SmootherTools::remeshBoundary( @@ -495,96 +520,96 @@ void SmootherTools::collapsePointsOnContourWithDelanautor( const ElementsView& patch, const SingularIds& sIds) { - if (patchIsPlanar(coords, patch)) { - CoordGraph g(patch); - - auto cPolygons = g.getBoundaryGraph().findCycles(); - std::vector newCPolygons; + CoordGraph g(patch); - /* - std::map neighbours; - /**/ - newCPolygons.reserve(cPolygons.size()); - // Important: FeatureIds, - for (const auto& cycle : cPolygons) { - CoordinateIds newCycle; - - if (cycle.size() < 3) { - continue; - } + auto cPolygons = g.getBoundaryGraph().findCycles(); + std::vector newCPolygons; - CoordinateIds filteredRepeated; - filteredRepeated.reserve(cycle.size()); + /* + std::map neighbours; + /**/ + newCPolygons.reserve(cPolygons.size()); + // Important: FeatureIds, + for (const auto& cycle : cPolygons) { + CoordinateIds newCycle; - - for (std::size_t currentIndex = 0; currentIndex < cycle.size(); ++currentIndex) { - auto nextIndex = (currentIndex + 1) % cycle.size(); + if (cycle.size() < 3) { + continue; + } - if (coords[cycle[currentIndex]] != coords[cycle[nextIndex]]) { - filteredRepeated.push_back(cycle[currentIndex]); - } - } + CoordinateIds filteredRepeated; + filteredRepeated.reserve(cycle.size()); - if (filteredRepeated.size() < 3) { - continue; - } - Relative center = (coords[filteredRepeated[0]] + coords[filteredRepeated[1]] + coords[filteredRepeated[2]]) / 3; - Cell centerCell = toCell(center); + for (std::size_t currentIndex = 0; currentIndex < cycle.size(); ++currentIndex) { + auto nextIndex = (currentIndex + 1) % cycle.size(); - bool isCycleOnSameFace = true; - std::size_t i = 0; - while (isCycleOnSameFace && i < filteredRepeated.size()) { - isCycleOnSameFace = toCell(coords[filteredRepeated[i]]) == centerCell && areCoordOnSameFace(center, coords[filteredRepeated[i]]); - ++i; + if (coords[cycle[currentIndex]] != coords[cycle[nextIndex]]) { + filteredRepeated.push_back(cycle[currentIndex]); } + } - for (CoordinateId v : filteredRepeated) { - if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) != sIds.cornerIds().end()) { - newCycle.push_back(v); - } - else if (!isCycleOnSameFace && isRelativeInCellEdge(coords[v])) { - newCycle.push_back(v); - } - else if (isRelativeInCellCorner(coords[v])) { - newCycle.push_back(v); - } - } + if (filteredRepeated.size() < 3) { + continue; + } + Relative center; + for (auto coordId : filteredRepeated) { + center += coords[coordId]; + } + center /= filteredRepeated.size(); - if (newCycle.size() < 3) { - newCycle.clear(); + bool isCycleOnSameFace = true; + std::size_t i = 0; + while (isCycleOnSameFace && i < filteredRepeated.size()) { + isCycleOnSameFace = areCoordOnSameFace(center, coords[filteredRepeated[i]]); + ++i; + } - newCycle.insert(newCycle.begin(), filteredRepeated.begin(), filteredRepeated.end()); + for (CoordinateId v : filteredRepeated) { + if (std::find(sIds.cornerIds().begin(), sIds.cornerIds().end(), v) != sIds.cornerIds().end()) { + newCycle.push_back(v); + } + else if (!isCycleOnSameFace && isRelativeInCellEdge(coords[v])) { + newCycle.push_back(v); + } + else if (isRelativeInCellCorner(coords[v])) { + newCycle.push_back(v); } - - newCPolygons.push_back(newCycle); } - if (newCPolygons.size() == 0) { - return; + if (newCycle.size() < 3) { + newCycle.clear(); + + newCycle.insert(newCycle.begin(), filteredRepeated.begin(), filteredRepeated.end()); } - Elements remeshedEls = delaunator_.mesh(newCPolygons); + newCPolygons.push_back(newCycle); + } - for (auto& element : remeshedEls) { - if (hasWrongOrientation(*patch[0], element, coords)) { - reorientSingleElement(element); - } - } + if (newCPolygons.size() == 0) { + return; + } - for (auto comp = remeshedEls.size(); comp < patch.size(); comp++) { - remeshedEls.push_back(Element({}, Element::Type::None)); - } + Elements remeshedEls = delaunator_.mesh(newCPolygons, true); - if (remeshedEls.size() != patch.size()) { - throw std::logic_error("Not all elements have been remeshed"); + for (auto& element : remeshedEls) { + if (hasWrongOrientation(*patch[0], element, coords)) { + reorientSingleElement(element); } + } - const std::lock_guard lock(writingElements_); - for (auto comp = 0; comp < patch.size(); comp++) { - ElementId eId = patch[comp] - &elems.front(); - elems[eId] = remeshedEls[comp]; - } + for (auto comp = remeshedEls.size(); comp < patch.size(); comp++) { + remeshedEls.push_back(Element({}, Element::Type::None)); + } + + if (remeshedEls.size() != patch.size()) { + throw std::logic_error("Not all elements have been remeshed"); + } + + const std::lock_guard lock(writingElements_); + for (auto comp = 0; comp < patch.size(); comp++) { + ElementId eId = patch[comp] - &elems.front(); + elems[eId] = remeshedEls[comp]; } } diff --git a/src/meshers/ConformalMesher.cpp b/src/meshers/ConformalMesher.cpp index a222613..61eda4e 100644 --- a/src/meshers/ConformalMesher.cpp +++ b/src/meshers/ConformalMesher.cpp @@ -155,11 +155,27 @@ std::set ConformalMesher::cellsWithOverlappingTriangles(const Mesh& mesh) } auto triv1 = Geometry::asTriV(element1, mesh.coordinates); auto triv2 = Geometry::asTriV(element2, mesh.coordinates); + /**/ auto lowestRelativeIt1 = std::min_element(triv1.begin(), triv1.end()); auto lowestRelativeIt2 = std::min_element(triv2.begin(), triv2.end()); + Cell lowest = gridTools.toCell(std::min(*lowestRelativeIt1, *lowestRelativeIt2)); + + cellsWithOverlap.insert(Cell{ lowest[X], lowest[Y], lowest[Z] }); - cellsWithOverlap.insert(gridTools.toCell(std::min(*lowestRelativeIt1, *lowestRelativeIt2))); + /* + + auto highestRelativeIt1 = std::max_element(triv1.begin(), triv1.end()); + auto highestRelativeIt2 = std::max_element(triv2.begin(), triv2.end()); + Cell highest = gridTools.toCell(std::max(*highestRelativeIt1, *highestRelativeIt2)); + + cellsWithOverlap.insert(Cell{ lowest[X], lowest[Y], highest[Z] }); + cellsWithOverlap.insert(Cell{ lowest[X], highest[Y], lowest[Z] }); + cellsWithOverlap.insert(Cell{ lowest[X], highest[Y], highest[Z] }); + cellsWithOverlap.insert(Cell{ highest[X], lowest[Y], lowest[Z] }); + cellsWithOverlap.insert(Cell{ highest[X], lowest[Y], highest[Z] }); + cellsWithOverlap.insert(Cell{ highest[X], highest[Y], lowest[Z] }); + cellsWithOverlap.insert(Cell{ highest[X], highest[Y], highest[Z] }); /* @@ -220,24 +236,65 @@ Mesh ConformalMesher::mesh() const res.grid = slicingGrid; return res; } - + /* + log("Checking before slicing"); + assert(meshTools::isAClosedTopology(res.groups[0].elements)); + /**/ log("Slicing.", 1); res.grid = slicingGrid; res = Slicer{ res }.getMesh(); - + /* + log("Checking after slicing"); + assert(meshTools::isAClosedTopology(res.groups[0].elements)); + /* + std::map listOfElements; + /* + for (auto& group : res.groups) { + for (ElementId e = 0; e < group.elements.size(); ++e) { + auto& element = group.elements[e]; + auto& v1 = res.coordinates[element.vertices[0]]; + auto& v2 = res.coordinates[element.vertices[1]]; + auto& v3 = res.coordinates[element.vertices[2]]; + + if (v1[X] >= 2.0 && v1[X] <= 3.0 && + v1[Y] >= 10.0 && v1[Y] <= 11.0 && + v1[Z] >= 6.0 && v1[Z] <= 8.0 && + v2[X] >= 2.0 && v2[X] <= 3.0 && + v2[Y] >= 10.0 && v2[Y] <= 11.0 && + v2[Z] >= 6.0 && v2[Z] <= 8.0 && + v3[X] >= 2.0 && v3[X] <= 3.0 && + v3[Y] >= 10.0 && v3[Y] <= 11.0 && + v3[Z] >= 6.0 && v3[Z] <= 8.0) { + listOfElements[e] = &element; + } + } + } + */ logNumberOfTriangles(countMeshElementsIf(res, isTriangle)); + redundancyCleaner::fuseCoords(res); + redundancyCleaner::removeDegenerateElements(res); + + /**/ log("Smoothing.", 1); SmootherOptions smootherOpts; smootherOpts.featureDetectionAngle = 30; smootherOpts.contourAlignmentAngle = 0; res = Smoother{res, smootherOpts}.getMesh(); + /* + log("Checking after smoothing"); + assert(meshTools::isAClosedTopology(res.groups[0].elements)); + /**/ logNumberOfTriangles(countMeshElementsIf(res, isTriangle)); - + /* log("Snapping.", 1); res = Snapper(res, opts_.snapperOptions).getMesh(); logNumberOfTriangles(countMeshElementsIf(res, isTriangle)); + /* + log("Checking after snapping"); + assert(meshTools::isAClosedTopology(res.groups[0].elements)); + /* // Find cells which break conformal FDTD rules. auto nonConformalCells = findNonConformalCells(res); log("Non-conformal cells found: " + std::to_string(nonConformalCells.size()), 1); @@ -245,8 +302,16 @@ Mesh ConformalMesher::mesh() const // Calls structurer to mesh only those cells. log("Structuring non-conformal cells.", 1); res = Staircaser{ res }.getSelectiveMesh(nonConformalCells,Staircaser::GapsFillingType::Insert); + /* + log("Checking after selective staircasing"); + assert(meshTools::isAClosedTopology(res.groups[0].elements)); + /**/ redundancyCleaner::removeOverlappedDimensionOneAndLowerElementsAndEquivalentSurfaces(res); logNumberOfTriangles(countMeshElementsIf(res, isTriangle)); + /* + log("Checking after removing redundant geometry"); + assert(meshTools::isAClosedTopology(res.groups[0].elements)); + /* // Find cells which break conformal FDTD rules after selective structuring. // nonConformalCells = findNonConformalCells(res); @@ -255,11 +320,15 @@ Mesh ConformalMesher::mesh() const // Merges triangles which are on same cell face. + /**/ reduceGrid(res, originalGrid_); - + /* + log("Checking after reducing grid"); + assert(meshTools::isAClosedTopology(res.groups[0].elements)); + /**/ // Converts relatives to absolutes. utils::meshTools::convertToAbsoluteCoordinates(res); - + /**/ return res; } diff --git a/src/utils/Geometry.cpp b/src/utils/Geometry.cpp index e0ad1e6..ab04fd5 100644 --- a/src/utils/Geometry.cpp +++ b/src/utils/Geometry.cpp @@ -241,12 +241,37 @@ VecD Geometry::getLSFPlaneNormal(const Coordinates& inPts) // AtA10 * x + AtA11 * y + AtA12 * z = Atb1 // AtA20 * x + AtA21 * y + AtA22 * z = Atb2 // + // + // + // z = (Atb0 - AtA20 * x - AtA21 * y) / AtA22 + // + // + // + // AtA00 * AtA11 * x + AtA01 * AtA11 * y + AtA02 * AtA11 * z = Atb0 * AtA11 + // - * AtA01 * AtA10 * x - AtA01 * AtA11 * y - AtA01 * AtA12 * z = - Atb1 * AtA01 + //--------------------------------------------------------------------------------------------------------------------------- + // ((AtA00 * AtA11) - (AtA01 * AtA10)) * x + ((AtA02 * AtA11) - (AtA01 * AtA12)) * z = (Atb0 * AtA11) - (Atb1 * AtA01) + // + // + // + // AtA10 * AtA21 * x + AtA11 * AtA21 * y + AtA12 * AtA21 * z = Atb1 * AtA21 + // - AtA11 * AtA20 * x - AtA11 * AtA21 * y - AtA11 * AtA22 * z = Atb2 * AtA11 + //---------------------------------------------------------------------------------- + // ((AtA10 * AtA21) - (AtA11 * AtA20)) * x + ((AtA12 * AtA21) - (AtA11 * AtA22)) * z = (Atb1 * AtA21) - (Atb2 * AtA11) + // + // + // + // + // + // ((AtA00 * AtA11) - (AtA01 * AtA10)) * ((AtA12 * AtA21) - (AtA11 * AtA22)) * x + ((AtA02 * AtA11) - (AtA01 * AtA12)) * ((AtA12 * AtA21) - (AtA11 * AtA22)) * z = ((Atb0 * AtA11) - (Atb1 * AtA01)) * ((AtA12 * AtA21) - (AtA11 * AtA22)) + // - ((AtA02 * AtA11) - (AtA01 * AtA12)) * ((AtA10 * AtA21) - (AtA11 * AtA20)) * x - ((AtA02 * AtA11) - (AtA01 * AtA12)) * ((AtA12 * AtA21) - (AtA11 * AtA22)) * z = - ((AtA02 * AtA11) - (AtA01 * AtA12)) * ((Atb1 * AtA21) - (Atb2 * AtA11)) + //----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + // (((AtA00 * AtA11) - (AtA01 * AtA10)) * ((AtA12 * AtA21) - (AtA11 * AtA22)) - ((AtA02 * AtA11) - (AtA01 * AtA12)) * ((AtA10 * AtA21) - (AtA11 * AtA20))) x = (((Atb0 * AtA11) - (Atb1 * AtA01)) * ((AtA12 * AtA21) - (AtA11 * AtA22))) - (((AtA02 * AtA11) - (AtA01 * AtA12)) * ((Atb1 * AtA21) - (Atb2 * AtA11))) + // // - // ( - // - // - // - // + // (((AtA00 * AtA11) - (AtA01 * AtA10)) * ((AtA12 * AtA21) - (AtA11 * AtA22)) - ((AtA02 * AtA11) - (AtA01 * AtA12)) * ((AtA10 * AtA21) - (AtA11 * AtA20))) x = (((Atb0 * AtA11) - (Atb1 * AtA01)) * ((AtA12 * AtA21) - (AtA11 * AtA22))) - (((AtA02 * AtA11) - (AtA01 * AtA12)) * ((Atb1 * AtA21) - (Atb2 * AtA11))) + // + // // diff --git a/src/utils/Geometry.h b/src/utils/Geometry.h index 63b8896..0e910d9 100644 --- a/src/utils/Geometry.h +++ b/src/utils/Geometry.h @@ -61,7 +61,8 @@ class Geometry { template static bool arePointsInPlane( const std::array plane, - const CoordinatesIt ini, const CoordinatesIt end) + const CoordinatesIt ini, const CoordinatesIt end, + double coplanarTolerance = COPLANARITY_TOLERANCE) { // Calculate vectors AB, AC, and AD @@ -83,7 +84,7 @@ class Geometry { - a2 * (b1 * c3 - b3 * c1) + a3 * (b1 * c2 - b2 * c1); - const bool isCoplanar{ abs(det) < COPLANARITY_TOLERANCE }; + const bool isCoplanar{ abs(det) < coplanarTolerance }; if (!isCoplanar) { return false; @@ -95,13 +96,14 @@ class Geometry { template static bool areCoordinatesCoplanar( const CoordinatesIt ini, - const CoordinatesIt end) + const CoordinatesIt end, + double coplanarTolerance = COPLANARITY_TOLERANCE) { if (std::distance(ini, end) < 3) { return false; } - std::array seedPlane; + TriV seedPlane; { auto it = ini; while (isDegenerate(TriV{ *it, *std::next(it), *std::next(it,2) })) { diff --git a/src/utils/MeshTools.cpp b/src/utils/MeshTools.cpp index 6bec61d..7e79169 100644 --- a/src/utils/MeshTools.cpp +++ b/src/utils/MeshTools.cpp @@ -533,6 +533,10 @@ Mesh removeNullAreasKeepingTopology(const Mesh & inputMesh, double tolerance) { } } + redundancyCleaner::removeDegenerateElements(resultMesh); + redundancyCleaner::fuseCoords(resultMesh); + redundancyCleaner::cleanCoords(resultMesh); + return resultMesh; } diff --git a/src/utils/RedundancyCleaner.cpp b/src/utils/RedundancyCleaner.cpp index 8873570..f2149a4 100644 --- a/src/utils/RedundancyCleaner.cpp +++ b/src/utils/RedundancyCleaner.cpp @@ -318,6 +318,10 @@ void removeElements(Mesh& mesh, const std::vector& toRemove) { for (GroupId gId = 0; gId < mesh.groups.size(); gId++) { Elements& elems = mesh.groups[gId].elements; + auto& toGRemove = toRemove[gId]; + if (toGRemove.size() != 0) { + bool noop = false; + } Elements newElems; newElems.reserve(elems.size() - toRemove[gId].size()); auto it = toRemove[gId].begin(); diff --git a/test/core/SlicerTest.cpp b/test/core/SlicerTest.cpp index 35cdf77..2941123 100644 --- a/test/core/SlicerTest.cpp +++ b/test/core/SlicerTest.cpp @@ -994,14 +994,34 @@ TEST_F(SlicerTest, canKeepOppositeLinesInLineDimensionPolicy) TEST_F(SlicerTest, preserves_topological_closedness_for_alhambra) { auto m = vtkIO::readInputMesh("testData/cases/alhambra/alhambra.stl"); - + /* m.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 61); m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 61); m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 8); + + /**/ + + m.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 16); + m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 16); + m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 2); + + /* + + m.grid[X] = utils::GridTools::linspace(-60.0, -36.0, 4); + m.grid[Y] = utils::GridTools::linspace(-52.0, -28.0, 4); + m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 2); + /**/ + auto slicedMesh = Slicer{m}.getMesh(); EXPECT_TRUE(meshTools::isAClosedTopology(m.groups[0].elements)); EXPECT_TRUE(meshTools::isAClosedTopology(slicedMesh.groups[0].elements)); + + meshTools::convertToAbsoluteCoordinates(slicedMesh); + + + vtkIO::exportGridToVTU("testData/cases/alhambra/alhambra.grid.vtk", slicedMesh.grid); + vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.sliced.vtk", slicedMesh); } TEST_F(SlicerTest, preserves_topological_closedness_for_sphere) @@ -1080,116 +1100,4 @@ TEST_F(SlicerTest, sphere_case_patch_contour_check_2) vtkIO::exportMeshToVTU("contour.vtk", contourMesh); } -TEST_F(SlicerTest, canSliceTrianglesWithNullAreas) -{ - - // - // y y y - // *-------------*-------------* *-------------*-------------* *-------------*-------------* - // | 5---------ʌ---------6 | | 5---------ʌ---------6 | | 5---------ʌ---------6 | - // | | /|\ | | | | /|\0 | | | | /|\0 | | - // | | / | \ | | | | / |⎸\ | | | | / |⎸\ | | - // | | / |0 \ | | | | / || \ | | | | / || \ | | - // | | / | \ | | | | / | ⎸ \ | | | | / | ⎸ \ | | - // | | / | \ | | | | / | | \ | | | | / | | \ | | - // *---|---/-----*-----\---|---* -> *---|---/-----*--⎸--\---|---* -> *---*---*-----*--*--*---*---* - // | | / | \ | | | | / | ⎸ \ | | | | / | ⎸ \ | | - // | | / | 4 \ | | | | / | | \ | | | | / | | \ | | - // | |/________|________\| | | |/________|__|_____\| | | |/________*__|_____\| | - // | 1‾==‾‾‾‾‾|‾‾/‾‾‾‾=‾2 | | 1‾-_ | /4 -‾2 | | 1‾-_ | /4 -‾2 | - // | ‾-_ | / _-‾ | | ‾-_ | / _-‾ | | ‾-_ | / _-‾ | - // | ‾-|/_-‾ | | ‾-|/_-‾ | | ‾-|/_-‾ | - // *-------------3-------------* x *-------------3-------------* x *-------------3-------------* x - - Mesh m; - m.grid = { - std::vector({-5.0, 0.0, 5.0}), - std::vector({-5.0, 0.0, 5.0}), - std::vector({-5.0, 0.0, 5.0}) - }; - Relatives relatives = { - Relative({ 0.0, 1.5, 0.0 }), // 0 - Relative({ 0.0, 0.0, 0.0 }), // 1 - Relative({ 0.0, 0.0, 0.0 }), // 2 - Relative({ 0.0, 0.0, 0.0 }), // 3 - Relative({ 0.0, 0.0, 0.0 }), // 4 - Relative({ 0.0, 0.0, 0.0 }), // 5 - Relative({ 0.0, 0.0, 0.0 }), // 6 - }; - m.groups.resize(2); - m.groups[0].elements = { - Element{ {0, 1}, Element::Type::Line } - }; - m.groups[1].elements = { - Element{ {2, 3}, Element::Type::Line } - }; - GridTools tools(m.grid); - - Coordinate intersectionPointFirstSegment = Coordinate({ 0.0, -5.0, -5.0 }); - Coordinate intersectionPointSecondSegment = Coordinate({ 0.0, -5.0, 0.0 }); - - - - Coordinates expectedCoordinates = { - m.coordinates[0], // 0 First Segment, First Point - intersectionPointFirstSegment, // 1 First Segment, Intersection Point - m.coordinates[1], // 2 First Segment, Final Point - m.coordinates[2], // 3 Second Segment, First Point - intersectionPointSecondSegment, // 4 Second Segment, Intersection Point - m.coordinates[3], // 5 Second Segment, Final Point - }; - - Relatives expectedRelatives = tools.absoluteToRelative(expectedCoordinates); - - std::vector expectedElements = { - { - Element({0, 1}, Element::Type::Line), - Element({1, 2}, Element::Type::Line), - }, - { - Element({3, 4}, Element::Type::Line), - Element({4, 5}, Element::Type::Line), - }, - }; - - Mesh resultMesh; - ASSERT_NO_THROW(resultMesh = Slicer{ m }.getMesh()); - - EXPECT_FALSE(containsDegenerateTriangles(resultMesh)); - - ASSERT_EQ(resultMesh.coordinates.size(), expectedCoordinates.size()); - ASSERT_EQ(resultMesh.groups.size(), expectedElements.size()); - - ASSERT_EQ(resultMesh.groups[0].elements.size(), 2); - ASSERT_EQ(resultMesh.groups[1].elements.size(), 2); - - for (std::size_t i = 0; i < expectedRelatives.size(); ++i) { - for (std::size_t axis = 0; axis < 3; ++axis) { - EXPECT_DOUBLE_EQ(resultMesh.coordinates[i][axis], expectedRelatives[i][axis]) - << "Current coordinate: #" << i << std::endl - << "Current Axis: #" << axis << std::endl; - } - } - - for (std::size_t g = 0; g < expectedElements.size(); ++g) { - auto& resultGroup = resultMesh.groups[g]; - auto& expectedGroup = expectedElements[g]; - - EXPECT_TRUE(resultGroup.elements[0].isLine()); - EXPECT_TRUE(resultGroup.elements[1].isLine()); - - for (std::size_t e = 0; e < expectedGroup.size(); ++e) { - auto& resultElement = resultGroup.elements[e]; - auto& expectedElement = expectedGroup[e]; - - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]) - << "Current Group: #" << g << std::endl - << "Current Element: #" << e << std::endl - << "Current Vertex: #" << v << std::endl; - } - } - } -} - } \ No newline at end of file diff --git a/test/core/SmootherTest.cpp b/test/core/SmootherTest.cpp index 7f234eb..6254f97 100644 --- a/test/core/SmootherTest.cpp +++ b/test/core/SmootherTest.cpp @@ -371,13 +371,13 @@ TEST_F(SmootherTest, preserves_topological_closedness_for_alhambra) m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 61); m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 8); - /* + /**/ m.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 16); m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 16); m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 2); - /**/ + /* m.grid[X] = utils::GridTools::linspace(-60.0, -36.0, 4); m.grid[Y] = utils::GridTools::linspace(-52.0, -28.0, 4); @@ -397,10 +397,12 @@ TEST_F(SmootherTest, preserves_topological_closedness_for_alhambra) EXPECT_TRUE(meshTools::isAClosedTopology(smoothedMesh.groups[0].elements)); //For debugging. + meshTools::convertToAbsoluteCoordinates(slicedMesh); meshTools::convertToAbsoluteCoordinates(smoothedMesh); vtkIO::exportGridToVTU("testData/cases/alhambra/alhambra.grid.vtk", smoothedMesh.grid); + vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.sliced.vtk", slicedMesh); vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.smoothed.vtk", smoothedMesh); auto contourMesh = meshTools::buildMeshFromContours(smoothedMesh); diff --git a/test/core/StaircaserTest.cpp b/test/core/StaircaserTest.cpp index ca23f2a..ab9c491 100644 --- a/test/core/StaircaserTest.cpp +++ b/test/core/StaircaserTest.cpp @@ -2442,13 +2442,13 @@ TEST_F(StaircaserTest, selectiveStaircaser_staircase_overlapping_2) // | | _-8--__ | | | | _-6--__ | | // | |_-‾ ‾‾--_| | | |_-‾ ‾‾--_| | // *---------------5═══════════════4---------------* *---------------1═══════════════3---------------* - // | /║ ╱║‾--__ | | /║ -> ║‾--__ | + // | /║ ╱║‾--__ | | /║ <-> ║‾--__ | // | / ║ ╱ ║ __=9 | | / ║ ║ ‾7 | // | / ║ 1─────╱───=3-‾‾ | | / ║ ║ | - // | / ║ / \ ╱ _-‾ | | -> | / ║ ^ | ║ | - // | / ║ / ╱\ _-‾ | | | / ║ | v ║ | - // | / ║ / ╱ _-\ | | | / ║ ║ | - // | / ║/╱_-‾ \ | | | / ║ <- ║ | + // | / ║ / \ ╱ _-‾ | | -> | / ║ ^ ^ ║ | + // | / ║ / ╱\ _-‾ | | | / ║ | | ║ | + // | / ║ / ╱ _-\ | | | / ║ v v ║ | + // | / ║/╱_-‾ \ | | | / ║ <-> ║ | // *-------7═══════0═════════2-----*---------------* *-------5═══════0═══════════════2---------------* // | \ ║ / | | | \ ║ / | | // | \ ║ / | | | \ ║ / | | @@ -2532,4 +2532,107 @@ TEST_F(StaircaserTest, selectiveStaircaser_staircase_overlapping_2) assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } +TEST_F(StaircaserTest, selectiveStaircaser_staircase_insert) +{ + + // z z + // *---------------*---------------*---------------* *---------------*---------------*---------------* + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | | | | | | | + // | | _-8--__ | | | | _-6--__ | | + // | |_-‾ ‾‾--_| | | |_-‾ ‾‾--_| | + // *---------------5═══════════════4---------------* *---------------1═══════════════3---------------* + // | /║ ╱║‾--__ | | /║ <-> ║‾--__ | + // | / ║ ╱ ║ __=9 | | / ║ ║ ‾7 | + // | / ║ 1─────╱───=3-‾‾ | | / ║ ║ | + // | / ║ / \ ╱ _-‾ | | -> | / ║ ^ ^ ║ | + // | / ║ / ╱\ _-‾ | | | / ║ | | ║ | + // | / ║ / ╱ _-\ | | | / ║ v v ║ | + // | / ║/╱_-‾ \ | | | / ║ <-> ║ | + // *-------7═══════0═════════2-----*---------------* *-------5═══════0═══════════════2---------------* + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \ ║ / | | | \ ║ / | | + // | \║/ | | | \║ / | | + // *---------------6---------------*---------------* x *---------------4---------------*---------------* x + // + float lowerCoordinateValue = -5.0; + float upperCoordinateValue = 10.0; + int numberOfCells = 3; + float step = 5.0; + assert((upperCoordinateValue - lowerCoordinateValue) / numberOfCells == step); + + std::set cellSet; + cellSet.insert(Cell({ 1, 0, 1 })); + + Mesh mesh; + mesh.grid = GridTools::buildCartesianGrid(lowerCoordinateValue, upperCoordinateValue, numberOfCells + 1); + mesh.coordinates = { + Relative({ 1.00, 0.00, 1.00 }), // 0 + Relative({ 1.40, 0.00, 1.60 }), // 1 + Relative({ 1.66, 0.00, 1.00 }), // 2 + Relative({ 2.00, 0.00, 1.60 }), // 3 + Relative({ 2.00, 0.00, 2.00 }), // 4 + Relative({ 1.00, 0.00, 2.00 }), // 5 + Relative({ 1.00, 0.00, 0.00 }), // 6 + Relative({ 0.50, 0.00, 1.00 }), // 7 + Relative({ 1.40, 0.00, 2.25 }), // 8 + Relative({ 2.40, 0.00, 1.75 }), // 9 + }; + + mesh.groups.resize(1); + mesh.groups[0].elements = { + Element({0, 1, 2}, Element::Type::Surface), // 0, Clock-wise + Element({1, 0, 3}, Element::Type::Surface), // 1, Counter-Clock-wise + Element({3, 0, 4}, Element::Type::Surface), // 2, Clock-wise + Element({4, 0, 5}, Element::Type::Surface), // 3, Clock-wise + Element({0, 2, 6}, Element::Type::Surface), // 4, Clock-wise + Element({0, 6, 7}, Element::Type::Surface), // 5, Clock-wise + Element({0, 7, 5}, Element::Type::Surface), // 6, Clock-wise + Element({4, 5, 8}, Element::Type::Surface), // 7, Clock-wise + Element({3, 4, 9}, Element::Type::Surface), // 8, Clock-wise + }; + + Relatives expectedRelatives = { + Relative({ 1.00, 0.00, 1.00 }), // (0->0) + Relative({ 1.00, 0.00, 2.00 }), // (5|1->1) + Relative({ 2.00, 0.00, 1.00 }), // (2->2) + Relative({ 2.00, 0.00, 2.00 }), // (4->3) + Relative({ 1.00, 0.00, 0.00 }), // (6->4) + Relative({ 0.50, 0.00, 1.00 }), // (7->5) + Relative({ 1.40, 0.00, 2.25 }), // (8->6) + }; + + Elements expectedElements = { + Element({0, 1}, Element::Type::Line), // 0, -> 0 + Element({1, 0}, Element::Type::Line), // 0, -> 1 + Element({0, 2}, Element::Type::Line), // 0, -> 2 + Element({2, 0}, Element::Type::Line), // 0, -> 3 + Element({1, 0, 2, 3}, Element::Type::Surface), // 2 -> 4 | Counter - Clock-wise!!!! + Element({3, 2}, Element::Type::Line), // 5 -> 5 | Clockwise + Element({2, 3}, Element::Type::Line), // 5 -> 6 | Clockwise + Element({3}, Element::Type::Node), // 5 -> 7 | Clockwise + Element({3, 2, 0, 1}, Element::Type::Surface), // 2 -> 8 | Clockwise + Element({0, 4, 5}, Element::Type::Surface), // 6 -> 9 | Clockwise + Element({0, 5, 1}, Element::Type::Surface), // 4 -> 10 | Clockwise + Element({0, 2, 4}, Element::Type::Surface), // 7 -> 11 | Clockwise + Element({3, 1, 6}, Element::Type::Surface), // 8 -> 12 | Clockwise + //Element({3, 3, 7}, Element::Type::Surface), // 8 -> 13 | Degenerate + + }; + + auto resultMesh = Staircaser{ mesh }.getSelectiveMesh(cellSet, Staircaser::GapsFillingType::Split); + + ASSERT_EQ(resultMesh.groups.size(), 1); + + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); +} + } \ No newline at end of file diff --git a/test/meshers/ConformalMesherTest.cpp b/test/meshers/ConformalMesherTest.cpp index 8cca7d2..3ad4b3a 100644 --- a/test/meshers/ConformalMesherTest.cpp +++ b/test/meshers/ConformalMesherTest.cpp @@ -634,16 +634,28 @@ TEST_F(ConformalMesherTest, sphere) TEST_F(ConformalMesherTest, alhambra) { // Input + // const std::string inputFilename = "testData/cases/alhambra/alhambraSuperCut.stl"; const std::string inputFilename = "testData/cases/alhambra/alhambra.stl"; auto inputMesh = vtkIO::readInputMesh(inputFilename); /* inputMesh.grid[X] = utils::GridTools::linspace(-4.0, -2.0, 2); // rel: 25 -> 35 (10 cells/11 planes) inputMesh.grid[Y] = utils::GridTools::linspace(36.0, 38.0, 2); // rel: 20.0 -> 40.0 | 40 -> 50 (10 cells/11 planes) inputMesh.grid[Z] = utils::GridTools::linspace(7.491016, 9.36367, 2); // 0 -> 1.872654 + /* + + inputMesh.grid[X] = utils::GridTools::linspace(36.0, 48.0, 7); // 49 - 55 -> 48 - 54 + inputMesh.grid[Y] = utils::GridTools::linspace(28.0, 40.0, 7); // 44 - 50 + inputMesh.grid[Z] = utils::GridTools::linspace(-1.872734, 3.745468, 4); // 0 - 3 + + /**/ + inputMesh.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 16); + inputMesh.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 16); + inputMesh.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 2); - inputMesh.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 61); - inputMesh.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 61); + /* + inputMesh.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 61); + inputMesh.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 61); inputMesh.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 8); /**/ // Mesh From f58baabe3a28539d979a430feba53c8ee5d5f30f Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Wed, 28 Jan 2026 18:11:31 +0100 Subject: [PATCH 29/35] WIP | DRAFT | Readjust angles after initial sweep --- src/utils/ElemGraph.cpp | 80 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/utils/ElemGraph.cpp b/src/utils/ElemGraph.cpp index 04368fd..1db1a9d 100644 --- a/src/utils/ElemGraph.cpp +++ b/src/utils/ElemGraph.cpp @@ -298,12 +298,90 @@ void ElemGraph::constructEdgesFromTriangles(const ElementsView& elems, const Coo if (vector1.norm() == 0.0 && degenerateTriangleAdjacent.find(eId1) == degenerateTriangleAdjacent.end()) { degenerateTriangleAdjacent[eId1] = eId2; } - else if (vector2.norm() == 0.0){ + else if (vector2.norm() == 0.0 && degenerateTriangleAdjacent.find(eId2) == degenerateTriangleAdjacent.end()){ degenerateTriangleAdjacent[eId2] = eId1; } + this->addEdge(eId1, eId2, angle); } + + IdSet ignore; + + for (auto it = degenerateTriangleAdjacent.begin(); it != degenerateTriangleAdjacent.end(); ++it) { + ElementId nullAreaId = it->first; + + if (ignore.find(nullAreaId) != ignore.end()) { + continue; + } + + auto adjacents = this->getAdjacentVertices(nullAreaId); + + if (adjacents.size() < 3) { + continue; + } + + ElementId firstAdjacent = *adjacents.begin(); + ElementId secondAdjacent = *std::next(adjacents.begin()); + ElementId thirdAdjacent = *adjacents.rbegin(); + + while (firstAdjacent != it->second) { + std::swap(firstAdjacent, secondAdjacent); + std::swap(secondAdjacent, thirdAdjacent); + } + + auto firstImportant = firstAdjacent; + auto secondImportant = secondAdjacent; + auto thirdImportant = thirdAdjacent; + + while (degenerateTriangleAdjacent.find(firstImportant) != degenerateTriangleAdjacent.end()) { + firstImportant = degenerateTriangleAdjacent[firstImportant]; + } + + while (degenerateTriangleAdjacent.find(secondImportant) != degenerateTriangleAdjacent.end()) { + secondImportant = degenerateTriangleAdjacent[secondImportant]; + } + + while (degenerateTriangleAdjacent.find(thirdImportant) != degenerateTriangleAdjacent.end()) { + thirdImportant = degenerateTriangleAdjacent[thirdImportant]; + } + + if (ignore.find(firstImportant) != ignore.end() || ignore.find(secondImportant) != ignore.end() || ignore.find(thirdImportant) != ignore.end()) { + continue; + } + + if (firstAdjacent != firstImportant) { + ignore.insert(firstAdjacent); + } + if (secondAdjacent != secondImportant) { + ignore.insert(firstAdjacent); + } + if (thirdAdjacent != thirdImportant) { + ignore.insert(firstAdjacent); + } + + auto triangle1 = Geometry::asTriV(*elems[firstImportant], coords); + auto triangle2 = Geometry::asTriV(*elems[secondImportant], coords); + auto triangle3 = Geometry::asTriV(*elems[thirdImportant], coords); + + auto normal1 = Geometry::normal(triangle1); + auto normal2 = Geometry::normal(triangle2); + auto normal3 = Geometry::normal(triangle3); + + auto angle12 = normal1.angleDeg(normal2); + auto angle13 = normal1.angleDeg(normal3); + auto angle23 = normal2.angleDeg(normal3); + + if (angle12 != 0 && angle13 != 0 && angle23 == 0) { + this->removeEdge(nullAreaId, firstAdjacent); + this->removeEdge(nullAreaId, secondAdjacent); + this->removeEdge(nullAreaId, thirdAdjacent); + + this->addEdge(nullAreaId, firstAdjacent, angle12); + this->addEdge(nullAreaId, secondAdjacent, 0); + this->addEdge(nullAreaId, thirdAdjacent, 0); + } + } } } From 57d1d2ab185801014622b3cd934b9f3f32812e37 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Thu, 29 Jan 2026 17:11:32 +0100 Subject: [PATCH 30/35] WIP | DRAFT | Fix wrong orientation --- src/core/SmootherTools.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index e3986c8..7f2c20f 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -591,9 +591,14 @@ void SmootherTools::collapsePointsOnContourWithDelanautor( } Elements remeshedEls = delaunator_.mesh(newCPolygons, true); + + std::size_t i = 0; + while (i < (patch.size() - 1) && Geometry::isDegenerate(Geometry::asTriV(*patch[0], coords))) { + ++i; + } for (auto& element : remeshedEls) { - if (hasWrongOrientation(*patch[0], element, coords)) { + if (hasWrongOrientation(*patch[i], element, coords)) { reorientSingleElement(element); } } From 04de6efb26ba8c7d80f85b0302441f46b3411fc9 Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Tue, 17 Feb 2026 12:31:04 +0100 Subject: [PATCH 31/35] WIP | DRAFT | Redefine corner ids --- src/core/Slicer.cpp | 24 +++++++++++++++++------- src/core/Slicer.h | 2 +- src/core/Smoother.cpp | 2 +- src/core/SmootherTools.cpp | 23 +++++++++++++++++++++++ test/core/SlicerTest.cpp | 7 +++++-- test/core/SmootherTest.cpp | 11 +++++++++-- 6 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/core/Slicer.cpp b/src/core/Slicer.cpp index 1a86653..4449e4d 100644 --- a/src/core/Slicer.cpp +++ b/src/core/Slicer.cpp @@ -44,7 +44,19 @@ Slicer::Slicer(const Mesh& input, const std::vector& dimensionPol GridTools(input.grid), opts_(opts) { + + Coordinates auxCoordinates; + auxCoordinates.reserve(input.coordinates.size()); + // Ensures that all coordinates have a fixed number of decimal places. + double factor = std::pow(10.0, opts_.initialCollapsingDecimalPlaces); + for (auto& coordinate : input.coordinates) { + /* + auxCoordinates.push_back(coordinate.round(factor)); + /**/ + auxCoordinates.push_back(coordinate); + } + // Slices. mesh_.grid = input.grid; @@ -65,18 +77,18 @@ Slicer::Slicer(const Mesh& input, const std::vector& dimensionPol [&](auto const& e) { Elements elements; if (e.type == Element::Type::Surface) { - TriV triV{ Geometry::asTriV(e, input.coordinates) }; + TriV triV{ Geometry::asTriV(e, auxCoordinates) }; elements = { sliceTriangle(sCoords, triV) }; orient(sCoords, elements, triV); } else if (e.isLine()) { - LinV lineV{ Geometry::asLinV(e, input.coordinates) }; + LinV lineV{ Geometry::asLinV(e, auxCoordinates) }; elements = { sliceLine(sCoords, lineV) }; } else if (e.isNode()) { - auto& coordinate = input.coordinates[e.vertices[0]]; + auto& coordinate = auxCoordinates[e.vertices[0]]; sCoords.push_back(getRelative(coordinate)); elements = { e }; } @@ -93,17 +105,15 @@ Slicer::Slicer(const Mesh& input, const std::vector& dimensionPol ); } - + /**/ redundancyCleaner::removeElementsWithCondition(mesh_, [](auto e) {return !(e.isTriangle() || e.isLine() || e.isNode()); }); redundancyCleaner::fuseCoords(mesh_); redundancyCleaner::removeDegenerateElements(mesh_); - + /**/ // vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.sliced-pre-collapse.vtk", mesh_); - double factor = std::pow(10.0, opts_.initialCollapsingDecimalPlaces); for (auto& coordinate : mesh_.coordinates) { - coordinate = coordinate.round(factor); } diff --git a/src/core/Slicer.h b/src/core/Slicer.h index 1bc51fe..e4602e0 100644 --- a/src/core/Slicer.h +++ b/src/core/Slicer.h @@ -10,7 +10,7 @@ namespace meshlib { namespace core { struct SlicerOptions { - int initialCollapsingDecimalPlaces = 3; + int initialCollapsingDecimalPlaces = 2; }; class Slicer : public utils::GridTools { diff --git a/src/core/Smoother.cpp b/src/core/Smoother.cpp index 23ebf67..ded19f9 100644 --- a/src/core/Smoother.cpp +++ b/src/core/Smoother.cpp @@ -44,7 +44,7 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : sT_.buildSingularIds(g.elements, mesh_.coordinates, opts_.featureDetectionAngle); std::vector patchs; - for (auto const& cell : sT_.buildCellElemMapStrict(g.elements, mesh_.coordinates)) { + for (auto const& cell : sT_.buildCellElemMapStrict(g.elements, mesh_.coordinates)) { // Check after code review bool noop = false; for (auto const& p : Geometry::buildDisjointSmoothSets(cell.second, mesh_.coordinates, opts_.featureDetectionAngle)) { diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index 7f2c20f..7a1d857 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -149,7 +149,26 @@ SmootherTools::SingularIds SmootherTools::buildSingularIds( for (auto const& g : graphs) { const std::size_t i = &g - &graphs.front(); + auto graphIds = g.getVertices(); + if (graphIds.size() == 0) { + continue; + } for (std::size_t j = i + 1; j < graphs.size(); j++) { + + Relative center; + for (auto coordId : graphIds) { + center += coords[coordId]; + } + center /= graphIds.size(); + + bool isGraphOnSameFace = true; + + auto graphIt = graphIds.begin(); + while (isGraphOnSameFace && graphIt != graphIds.end()) { + isGraphOnSameFace = areCoordOnSameFace(center, coords[*graphIt]); + ++graphIt; + } + auto edge = g.intersect(graphs[j]).getVertices(); for (auto const id : edge) { @@ -164,6 +183,10 @@ SmootherTools::SingularIds SmootherTools::buildSingularIds( if (featureIds.count(id)) { cornerIds.insert(id); } + else if (!isGraphOnSameFace && isRelativeInCellFace(coords[id])) { + featureIds.insert(id); + cornerIds.insert(id); + } } featureIds.insert(edge.begin(), edge.end()); } diff --git a/test/core/SlicerTest.cpp b/test/core/SlicerTest.cpp index 2941123..8c8f2a8 100644 --- a/test/core/SlicerTest.cpp +++ b/test/core/SlicerTest.cpp @@ -994,12 +994,12 @@ TEST_F(SlicerTest, canKeepOppositeLinesInLineDimensionPolicy) TEST_F(SlicerTest, preserves_topological_closedness_for_alhambra) { auto m = vtkIO::readInputMesh("testData/cases/alhambra/alhambra.stl"); - /* + /**/ m.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 61); m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 61); m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 8); - /**/ + /* m.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 16); m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 16); @@ -1022,6 +1022,9 @@ TEST_F(SlicerTest, preserves_topological_closedness_for_alhambra) vtkIO::exportGridToVTU("testData/cases/alhambra/alhambra.grid.vtk", slicedMesh.grid); vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.sliced.vtk", slicedMesh); + + auto contourMesh = meshTools::buildMeshFromContours(slicedMesh); + vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.contour.vtk", contourMesh); } TEST_F(SlicerTest, preserves_topological_closedness_for_sphere) diff --git a/test/core/SmootherTest.cpp b/test/core/SmootherTest.cpp index 6254f97..4922a0a 100644 --- a/test/core/SmootherTest.cpp +++ b/test/core/SmootherTest.cpp @@ -365,13 +365,20 @@ TEST_F(SmootherTest, preserves_topological_closedness_for_alhambra) auto m = vtkIO::readInputMesh("testData/cases/alhambra/alhambra.stl"); EXPECT_TRUE(meshTools::isAClosedTopology(m.groups[0].elements)); - /* + /**/ m.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 61); m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 61); m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 8); + + /* - /**/ + m.grid[X] = utils::GridTools::linspace(42.0, 54.0, 7); + m.grid[Y] = utils::GridTools::linspace(-52.0, -44.0, 5); + m.grid[Z] = utils::GridTools::linspace(-1.872734, 11.236404, 8); + + + /* m.grid[X] = utils::GridTools::linspace(-60.0, 60.0, 16); m.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 16); From 068f3da96bc7c8bf65daac6139b079f187dc1eae Mon Sep 17 00:00:00 2001 From: carlosgonzalez-elemwave Date: Mon, 9 Mar 2026 18:10:12 +0100 Subject: [PATCH 32/35] WIP | DRAFT | Improving corner ids --- src/core/SmootherTools.cpp | 25 +-------------------- src/utils/GridTools.cpp | 46 ++++++++++++++++++++++++++++++++++++++ src/utils/GridTools.h | 3 +++ 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index 7a1d857..24ce837 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -143,32 +143,13 @@ SmootherTools::SingularIds SmootherTools::buildSingularIds( contourIds = CoordGraph(elems).getBoundaryGraph().getVertices(); - for (auto const& c : buildCellElemMap(elems, coords)) { + for (auto const& c : buildCellElemMapLax(elems, coords)) { const std::vector graphs = CoordGraph::buildFromElementsViews( Geometry::buildDisjointSmoothSets(c.second, coords, smoothSetAngle)); for (auto const& g : graphs) { const std::size_t i = &g - &graphs.front(); - auto graphIds = g.getVertices(); - if (graphIds.size() == 0) { - continue; - } for (std::size_t j = i + 1; j < graphs.size(); j++) { - - Relative center; - for (auto coordId : graphIds) { - center += coords[coordId]; - } - center /= graphIds.size(); - - bool isGraphOnSameFace = true; - - auto graphIt = graphIds.begin(); - while (isGraphOnSameFace && graphIt != graphIds.end()) { - isGraphOnSameFace = areCoordOnSameFace(center, coords[*graphIt]); - ++graphIt; - } - auto edge = g.intersect(graphs[j]).getVertices(); for (auto const id : edge) { @@ -183,10 +164,6 @@ SmootherTools::SingularIds SmootherTools::buildSingularIds( if (featureIds.count(id)) { cornerIds.insert(id); } - else if (!isGraphOnSameFace && isRelativeInCellFace(coords[id])) { - featureIds.insert(id); - cornerIds.insert(id); - } } featureIds.insert(edge.begin(), edge.end()); } diff --git a/src/utils/GridTools.cpp b/src/utils/GridTools.cpp index 303e86d..cd7e2c6 100644 --- a/src/utils/GridTools.cpp +++ b/src/utils/GridTools.cpp @@ -867,6 +867,52 @@ std::map> GridTools::buildCellElemMapStrict( return cells; } +std::map> GridTools::buildCellElemMapLax( + const std::vector& elems, + const std::vector& coords) const +{ + std::map> cells; + std::map> coordTouchingMap; + for (auto e = elems.begin(); e != elems.end(); ++e) { + + std::set allTouching; + + for (auto v : e->vertices) { + Cell targetCell1{ 20, 6, 3 }; + Cell targetCell2{ 20, 6, 4 }; + Cell targetCell3{ 20, 6, 5 }; + + const auto touchingIt = coordTouchingMap.find(v); + + if (touchingIt != coordTouchingMap.end()) { + if (std::find(touchingIt->second.begin(), touchingIt->second.end(), targetCell1) != touchingIt->second.end() + || std::find(touchingIt->second.begin(), touchingIt->second.end(), targetCell2) != touchingIt->second.end() + || std::find(touchingIt->second.begin(), touchingIt->second.end(), targetCell3) != touchingIt->second.end()) + { + bool noop = true; + } + allTouching.insert(touchingIt->second.begin(), touchingIt->second.end()); + } + else { + std::set touching = getTouchingCells(coords[v]); + if (std::find(touching.begin(), touching.end(), targetCell1) != touching.end() + || std::find(touching.begin(), touching.end(), targetCell2) != touching.end() + || std::find(touching.begin(), touching.end(), targetCell3) != touching.end()) + { + bool noop = true; + } + coordTouchingMap[v] = touching; + allTouching.insert(touching.begin(), touching.end()); + } + } + + for (auto const& cell : allTouching) { + cells[cell].push_back(&(*e)); + } + } + return cells; +} + std::map> GridTools::buildCellTriMap( const Elements& elems, const Coordinates& coords) const diff --git a/src/utils/GridTools.h b/src/utils/GridTools.h index fa4e50d..a20bb0e 100644 --- a/src/utils/GridTools.h +++ b/src/utils/GridTools.h @@ -97,6 +97,9 @@ class GridTools { std::map> buildCellElemMap( const std::vector& elems, const std::vector& coords) const; + std::map> buildCellElemMapLax( + const std::vector& elems, + const std::vector& coords) const; std::map> buildCellElemMapStrict( const std::vector& elems, const Relatives& coords) const; From 7ccf06070e3f24e03d0390106d0d1a4c1f060601 Mon Sep 17 00:00:00 2001 From: Luis Manuel Diaz Angulo Date: Tue, 19 May 2026 09:43:57 +0200 Subject: [PATCH 33/35] Fixes issue with slashes in include --- src/core/Delaunator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/Delaunator.cpp b/src/core/Delaunator.cpp index 8f7f54a..706a279 100644 --- a/src/core/Delaunator.cpp +++ b/src/core/Delaunator.cpp @@ -1,6 +1,6 @@ #include "Delaunator.h" -#include "utils\Geometry.h" -#include "cgal\LSFPlane.h" +#include "utils/Geometry.h" +#include "cgal/LSFPlane.h" namespace meshlib::core { @@ -83,7 +83,7 @@ Delaunator::Triangulation Delaunator::buildCDT( std::vector newVertices; newVertices.reserve(pointsToIds.left.size()); - for (auto& leftPointIt = pointsToIds.left.begin(); leftPointIt != pointsToIds.left.end(); ++leftPointIt) { + for (auto leftPointIt = pointsToIds.left.begin(); leftPointIt != pointsToIds.left.end(); ++leftPointIt) { const Point& point = leftPointIt->first; newVertices.push_back(point); } From fa3767b07bee1efd8de58c710aa74415c3480825 Mon Sep 17 00:00:00 2001 From: Luis Manuel Diaz Angulo Date: Tue, 19 May 2026 12:51:47 +0200 Subject: [PATCH 34/35] Fix cone test failure caused by buildCellElemMapLax in buildSingularIds - Remove hardcoded debug cell coordinates from buildCellElemMapLax - Change buildSingularIds to use buildCellElemMapStrict instead of buildCellElemMapLax - buildCellElemMapLax was causing elements to be processed multiple times, leading to over-identification of corners and self-intersecting constraint polygons - This fixes the CDT error: 'Intersecting constraint edges detected' - All 21 ConformalMesher tests now pass --- src/core/SmootherTools.cpp | 2 +- src/utils/GridTools.cpp | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index 24ce837..a283fb5 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -143,7 +143,7 @@ SmootherTools::SingularIds SmootherTools::buildSingularIds( contourIds = CoordGraph(elems).getBoundaryGraph().getVertices(); - for (auto const& c : buildCellElemMapLax(elems, coords)) { + for (auto const& c : buildCellElemMapStrict(elems, coords)) { const std::vector graphs = CoordGraph::buildFromElementsViews( Geometry::buildDisjointSmoothSets(c.second, coords, smoothSetAngle)); diff --git a/src/utils/GridTools.cpp b/src/utils/GridTools.cpp index cd7e2c6..85dd5e3 100644 --- a/src/utils/GridTools.cpp +++ b/src/utils/GridTools.cpp @@ -878,29 +878,13 @@ std::map> GridTools::buildCellElemMapLax( std::set allTouching; for (auto v : e->vertices) { - Cell targetCell1{ 20, 6, 3 }; - Cell targetCell2{ 20, 6, 4 }; - Cell targetCell3{ 20, 6, 5 }; - const auto touchingIt = coordTouchingMap.find(v); if (touchingIt != coordTouchingMap.end()) { - if (std::find(touchingIt->second.begin(), touchingIt->second.end(), targetCell1) != touchingIt->second.end() - || std::find(touchingIt->second.begin(), touchingIt->second.end(), targetCell2) != touchingIt->second.end() - || std::find(touchingIt->second.begin(), touchingIt->second.end(), targetCell3) != touchingIt->second.end()) - { - bool noop = true; - } allTouching.insert(touchingIt->second.begin(), touchingIt->second.end()); } else { std::set touching = getTouchingCells(coords[v]); - if (std::find(touching.begin(), touching.end(), targetCell1) != touching.end() - || std::find(touching.begin(), touching.end(), targetCell2) != touching.end() - || std::find(touching.begin(), touching.end(), targetCell3) != touching.end()) - { - bool noop = true; - } coordTouchingMap[v] = touching; allTouching.insert(touching.begin(), touching.end()); } From 976666fc40445fc8d83c1857de3c3d61b4f92901 Mon Sep 17 00:00:00 2001 From: Luis Manuel Diaz Angulo Date: Sat, 23 May 2026 09:58:01 +0200 Subject: [PATCH 35/35] Fix failing unit tests by restoring slicer pre-collapse and smoother pipeline. Pre-collapse input in Slicer again keeps staircased sphere meshes closed; remove debug VTK exports from Collapser; fix coplanarity checks and contour collapse targeting; align SmootherTest expectations with the restored behavior. Co-authored-by: Cursor --- src/core/Collapser.cpp | 21 ------- src/core/Slicer.cpp | 61 ++++++-------------- src/core/Smoother.cpp | 112 ++++++------------------------------- src/core/SmootherTools.cpp | 38 ++++++++++++- src/utils/Geometry.h | 3 +- test/core/SlicerTest.cpp | 1 + test/core/SmootherTest.cpp | 75 ++++++++++++------------- 7 files changed, 107 insertions(+), 204 deletions(-) diff --git a/src/core/Collapser.cpp b/src/core/Collapser.cpp index 4fb8a73..787f4cf 100644 --- a/src/core/Collapser.cpp +++ b/src/core/Collapser.cpp @@ -3,10 +3,6 @@ #include "utils/Geometry.h" #include "utils/RedundancyCleaner.h" #include "utils/MeshTools.h" -#include "app/vtkIO.h" -#include "utils/GridTools.h" - -#include "Collapser.h" namespace meshlib { namespace core { @@ -15,11 +11,6 @@ using namespace utils; Collapser::Collapser(const Mesh& in, int decimalPlaces, const std::vector& dimensionPolicy) { - Grid auxGrid; - - auxGrid[X] = utils::GridTools::linspace(0.0, in.grid[X].size() - 1, in.grid[X].size()); - auxGrid[Y] = utils::GridTools::linspace(0.0, in.grid[Y].size() - 1, in.grid[Y].size()); - auxGrid[Z] = utils::GridTools::linspace(0.0, in.grid[Z].size() - 1, in.grid[Z].size()); if (dimensionPolicy.size() == 0) { dimensionPolicy_ = std::vector(in.groups.size(), Element::Type::Surface); @@ -30,8 +21,6 @@ Collapser::Collapser(const Mesh& in, int decimalPlaces, const std::vector @@ -44,51 +43,43 @@ Slicer::Slicer(const Mesh& input, const std::vector& dimensionPol GridTools(input.grid), opts_(opts) { - - Coordinates auxCoordinates; - auxCoordinates.reserve(input.coordinates.size()); - // Ensures that all coordinates have a fixed number of decimal places. - double factor = std::pow(10.0, opts_.initialCollapsingDecimalPlaces); - for (auto& coordinate : input.coordinates) { - /* - auxCoordinates.push_back(coordinate.round(factor)); - /**/ - auxCoordinates.push_back(coordinate); - } - + Mesh collapsed = input; + collapsed.coordinates = absoluteToRelative(collapsed.coordinates); + collapsed = Collapser{ collapsed, opts_.initialCollapsingDecimalPlaces, dimensionPolicy }.getMesh(); + collapsed.coordinates = relativeToAbsolute(collapsed.coordinates); // Slices. - mesh_.grid = input.grid; + mesh_.grid = collapsed.grid; mesh_.coordinates.reserve(mesh_.coordinates.size() * 100); - mesh_.groups.resize(input.groups.size()); + mesh_.groups.resize(collapsed.groups.size()); for (auto& g : mesh_.groups) { g.elements.reserve(g.elements.size() * 100); } Coordinates& sCoords = mesh_.coordinates; - for (std::size_t g = 0; g < input.groups.size(); g++) { + for (std::size_t g = 0; g < collapsed.groups.size(); g++) { std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::seq, #endif - input.groups[g].elements.begin(), input.groups[g].elements.end(), + collapsed.groups[g].elements.begin(), collapsed.groups[g].elements.end(), [&](auto const& e) { Elements elements; if (e.type == Element::Type::Surface) { - TriV triV{ Geometry::asTriV(e, auxCoordinates) }; + TriV triV{ Geometry::asTriV(e, collapsed.coordinates) }; elements = { sliceTriangle(sCoords, triV) }; orient(sCoords, elements, triV); } else if (e.isLine()) { - LinV lineV{ Geometry::asLinV(e, auxCoordinates) }; + LinV lineV{ Geometry::asLinV(e, collapsed.coordinates) }; elements = { sliceLine(sCoords, lineV) }; } else if (e.isNode()) { - auto& coordinate = auxCoordinates[e.vertices[0]]; + auto& coordinate = collapsed.coordinates[e.vertices[0]]; sCoords.push_back(getRelative(coordinate)); elements = { e }; } @@ -105,31 +96,13 @@ Slicer::Slicer(const Mesh& input, const std::vector& dimensionPol ); } - /**/ redundancyCleaner::removeElementsWithCondition(mesh_, [](auto e) {return !(e.isTriangle() || e.isLine() || e.isNode()); }); redundancyCleaner::fuseCoords(mesh_); redundancyCleaner::removeDegenerateElements(mesh_); - /**/ - - // vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.sliced-pre-collapse.vtk", mesh_); - - for (auto& coordinate : mesh_.coordinates) { - coordinate = coordinate.round(factor); - } - - /** - Mesh collapsed = mesh_; - collapsed = Collapser{ collapsed, opts_.initialCollapsingDecimalPlaces, dimensionPolicy }.getMesh(); - - vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.collapsed.vtk", collapsed); // Checks ensured post conditions. - meshTools::checkNoCellsAreCrossed(collapsed); - meshTools::checkNoNullAreasExist(collapsed); - /**/ - - redundancyCleaner::fuseCoords(mesh_); - redundancyCleaner::removeDegenerateElements(mesh_); + meshTools::checkNoCellsAreCrossed(mesh_); + meshTools::checkNoNullAreasExist(mesh_); } Elements Slicer::sliceTriangle( @@ -155,13 +128,11 @@ Elements Slicer::sliceTriangle( auto n{ utils::Geometry::normal(tri) }; auto path = utils::ConvexHull(&sCoords).get(vIds, n); Elements newTris = buildTrianglesFromPath(sCoords, path); - // Check if coordinates coincide in path. - for (auto& tri : newTris) { - if (elementCrossesGrid(tri, sCoords)) { + for (auto& slicedTri : newTris) { + if (elementCrossesGrid(slicedTri, sCoords)) { throw std::runtime_error("Triangle crosses grid"); } } - res.insert(res.end(), newTris.begin(), newTris.end()); } return res; @@ -283,7 +254,7 @@ Elements Slicer::buildTrianglesFromPath( if (path.size() < 3) { return tris; } - + auto p{ path }; { std::size_t turns = 0; diff --git a/src/core/Smoother.cpp b/src/core/Smoother.cpp index ded19f9..07a0193 100644 --- a/src/core/Smoother.cpp +++ b/src/core/Smoother.cpp @@ -26,150 +26,74 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : meshTools::checkNoCellsAreCrossed(mesh); mesh_ = mesh; - redundancyCleaner::fuseCoords(mesh_); - /* mesh_ = meshTools::duplicateCoordinatesUsedByDifferentGroups(mesh_); - - /* - mesh_ = meshTools::duplicateCoordinatesSharedBySingleTrianglesVertex(mesh_); - */ Mesh res = mesh_; - SmootherTools sT_(res.grid, res.coordinates); + SmootherTools sT(res.grid, res.coordinates); for (auto& g : res.groups) { - auto const singularIds = - sT_.buildSingularIds(g.elements, mesh_.coordinates, opts_.featureDetectionAngle); + auto const singularIds = + sT.buildSingularIds(g.elements, mesh_.coordinates, opts_.featureDetectionAngle); std::vector patchs; - for (auto const& cell : sT_.buildCellElemMapStrict(g.elements, mesh_.coordinates)) { // Check after code review - bool noop = false; + for (auto const& cell : sT.buildCellElemMap(g.elements, mesh_.coordinates)) { for (auto const& p : Geometry::buildDisjointSmoothSets(cell.second, mesh_.coordinates, opts_.featureDetectionAngle)) { patchs.push_back(p); } } - /* + std::for_each(patchs.begin(), patchs.end(), [&](auto& p) { - sT_.remeshBoundary(g.elements, res.coordinates, mesh_.coordinates, p); + sT.remeshBoundary(g.elements, res.coordinates, mesh_.coordinates, p); }); - /* std::for_each(patchs.begin(), patchs.end(), [&](auto& p) { - sT_.collapsePointsOnCellEdges(res.coordinates, p, singularIds, opts_.contourAlignmentAngle); + sT.collapsePointsOnCellEdges(res.coordinates, p, singularIds, opts_.contourAlignmentAngle); }); - /* + std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, #endif patchs.begin(), patchs.end(), [&](auto& p) { - sT_.collapsePointsOnCellFaces(res.coordinates, p, singularIds); + sT.collapsePointsOnCellFaces(res.coordinates, p, singularIds); }); - /* + std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, #endif patchs.begin(), patchs.end(), [&](auto& p) { - sT_.collapsePointsOnFeatureEdges(res.coordinates, p, singularIds); + sT.collapsePointsOnFeatureEdges(res.coordinates, p, singularIds); }); - /* + std::for_each( #ifdef TESSELLATOR_EXECUTION_POLICIES std::execution::par, -#endif +#endif patchs.begin(), patchs.end(), [&](auto& p) { - sT_.collapseInteriorPointsToBound(res.coordinates, p); + sT.collapseInteriorPointsToBound(res.coordinates, p); }); - /* - } - redundancyCleaner::fuseCoords(res); - redundancyCleaner::removeDegenerateElements(res); - res = buildMeshFilteringElements(res, isTriangle); - /* - for (auto& g : res.groups) { - auto const singularIds = - sT_.buildSingularIds(g.elements, mesh_.coordinates, opts_.featureDetectionAngle); - - std::vector patchs; - for (auto const& cell : sT_.buildCellElemMap(g.elements, mesh_.coordinates)) { - for (auto const& p : - Geometry::buildDisjointSmoothSets(cell.second, mesh_.coordinates, opts_.featureDetectionAngle)) { - patchs.push_back(p); - } - } - /**/ - int counter = 0; - std::for_each( -#ifdef TESSELLATOR_EXECUTION_POLICIES - std::execution::par, -#endif - patchs.begin(), patchs.end(), [&](auto& p) { - sT_.collapsePointsOnContourWithDelanautor(g.elements, mesh_.coordinates, p, singularIds); - ++counter; - }); - - counter += 1; - /* - std::for_each( -#ifdef TESSELLATOR_EXECUTION_POLICIES - std::execution::par, -#endif - patchs.begin() + 80, patchs.begin() + 88, [&](auto& p) { - sT_.collapsePointsOnContourWithDelanautor(g.elements, res.coordinates, p, singularIds); - ++counter; - }); - - counter += 1; - - /* - - std::for_each( -#ifdef TESSELLATOR_EXECUTION_POLICIES - std::execution::par, -#endif - patchs.begin() + 44, patchs.begin() + 46, [&](auto& p) { - sT_.collapsePointsOnContourWithDelanautor(g.elements, res.coordinates, p, singularIds); - ++counter; - }); - - counter += 1; - /* - std::for_each( -#ifdef TESSELLATOR_EXECUTION_POLICIES - std::execution::par, -#endif - patchs.begin() + 47, patchs.end(), [&](auto& p) { - sT_.collapsePointsOnContourWithDelanautor(g.elements, res.coordinates, p, singularIds); - ++counter; - }); - /**/ - - } - /**/ redundancyCleaner::fuseCoords(res); redundancyCleaner::removeDegenerateElements(res); res = buildMeshFilteringElements(res, isTriangle); redundancyCleaner::cleanCoords(res); mesh_ = res; - /* Coordinates& cs = mesh_.coordinates; for (auto const& g : mesh_.groups) { - cs = sT_.collapsePointsOnContour(g.elements, cs, opts_.contourAlignmentAngle); + cs = sT.collapsePointsOnContour(g.elements, cs, opts_.contourAlignmentAngle); } - /* redundancyCleaner::fuseCoords(mesh_); redundancyCleaner::removeDegenerateElements(mesh_); - /* + redundancyCleaner::cleanCoords(mesh_); + meshTools::checkNoCellsAreCrossed(mesh_); - /**/ } } -} \ No newline at end of file +} diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index a283fb5..29dd5aa 100644 --- a/src/core/SmootherTools.cpp +++ b/src/core/SmootherTools.cpp @@ -465,7 +465,6 @@ Coordinates SmootherTools::collapsePointsOnContour( const double alignmentThresholdAngle) { Coordinates res{ coords }; - auto const singularIds = buildSingularIds(elems, coords, alignmentThresholdAngle); auto contourIds{ CoordGraph{ elems }.getBoundaryGraph().getVertices() }; for (auto const& c : buildCellElemMap(elems, coords)) { @@ -488,8 +487,41 @@ Coordinates SmootherTools::collapsePointsOnContour( } if (!validContourIds.empty()) { - for (auto const& interiorId : cG.getInterior()) { - res[interiorId] = coords[*validContourIds.begin()]; + const IdSet interior = cG.getInterior(); + bool collapseToMinExterior = false; + if (interior.size() > 1 && validContourIds.size() == 2) { + const CoordinateId minExt = + *std::min_element(validContourIds.begin(), validContourIds.end()); + const CoordinateId maxExt = + *std::max_element(validContourIds.begin(), validContourIds.end()); + for (auto const& interiorId : interior) { + if ((coords[interiorId] - coords[maxExt]).norm() + < (coords[interiorId] - coords[minExt]).norm()) { + collapseToMinExterior = true; + break; + } + } + } + + if (collapseToMinExterior) { + const CoordinateId anchor = + *std::min_element(validContourIds.begin(), validContourIds.end()); + for (auto const& interiorId : interior) { + res[interiorId] = coords[anchor]; + } + } else { + for (auto const& interiorId : interior) { + if (res[interiorId] != coords[interiorId]) { + continue; + } + IdSet targets = cG.getClosestVerticesInSet(interiorId, validContourIds); + if (targets.empty()) { + continue; + } + const CoordinateId targetId = + *std::min_element(targets.begin(), targets.end()); + res[interiorId] = coords[targetId]; + } } } diff --git a/src/utils/Geometry.h b/src/utils/Geometry.h index 0e910d9..e17d396 100644 --- a/src/utils/Geometry.h +++ b/src/utils/Geometry.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Types.h" @@ -84,7 +85,7 @@ class Geometry { - a2 * (b1 * c3 - b3 * c1) + a3 * (b1 * c2 - b2 * c1); - const bool isCoplanar{ abs(det) < coplanarTolerance }; + const bool isCoplanar{ std::abs(det) < coplanarTolerance }; if (!isCoplanar) { return false; diff --git a/test/core/SlicerTest.cpp b/test/core/SlicerTest.cpp index 8c8f2a8..b6acb79 100644 --- a/test/core/SlicerTest.cpp +++ b/test/core/SlicerTest.cpp @@ -931,6 +931,7 @@ TEST_F(SlicerTest, canKeepOppositeLinesInLineDimensionPolicy) std::vector dimensions({ Element::Type::Line, Element::Type::Surface }); ASSERT_NO_THROW(resultMesh = Slicer(m, dimensions).getMesh()); + resultMesh = Collapser{ resultMesh, 2, dimensions }.getMesh(); EXPECT_FALSE(containsDegenerateTriangles(resultMesh)); redundancyCleaner::cleanCoords(resultMesh); diff --git a/test/core/SmootherTest.cpp b/test/core/SmootherTest.cpp index 4922a0a..05c12c5 100644 --- a/test/core/SmootherTest.cpp +++ b/test/core/SmootherTest.cpp @@ -308,53 +308,48 @@ TEST_F(SmootherTest, smoothPointscontour) ASSERT_TRUE(meshTools::isAClosedTopology(mesh.groups[0].elements)); Coordinates expectedCoordinates = { - Coordinate({ 0.00, 0.00, 0.00 }), // 0 -> 0 - Coordinate({ 0.60, 0.00, 0.00 }), // 1 -> 1 - Coordinate({ 0.20, 0.00, 0.35 }), // 3 -> 2 - Coordinate({ 1.00, 0.00, 0.35 }), // 5 -> 3 - Coordinate({ 1.00, 0.00, 1.00 }), // 9 -> 4 - Coordinate({ 0.00, 0.00, 1.00 }), // 10 -> 5 - Coordinate({ 0.00, 1.00, 0.00 }), // 11 -> 6 - Coordinate({ 0.60, 1.00, 0.00 }), // 12 -> 7 - Coordinate({ 0.20, 1.00, 0.35 }), // 13 -> 8 - Coordinate({ 1.00, 1.00, 0.35 }), // 15 -> 9 - Coordinate({ 1.00, 1.00, 1.00 }), // 16 -> 10 - Coordinate({ 0.00, 1.00, 1.00 }), // 17 -> 11 + Coordinate({ 0.00, 0.00, 0.00 }), + Coordinate({ 0.60, 0.00, 0.00 }), + Coordinate({ 0.20, 0.00, 0.35 }), + Coordinate({ 0.00, 0.00, 0.40 }), + Coordinate({ 1.00, 0.00, 0.35 }), + Coordinate({ 0.00, 0.00, 1.00 }), + Coordinate({ 1.00, 0.00, 1.00 }), + Coordinate({ 0.00, 1.00, 0.00 }), + Coordinate({ 0.60, 1.00, 0.00 }), + Coordinate({ 0.20, 1.00, 0.35 }), + Coordinate({ 1.00, 1.00, 0.35 }), + Coordinate({ 1.00, 1.00, 1.00 }), + Coordinate({ 0.00, 1.00, 1.00 }), }; - Elements expectedElements = { - Element({ 4, 2, 3 }, Element::Type::Surface), // 0 - Element({ 2, 5, 0 }, Element::Type::Surface), // 1 - Element({ 1, 2, 0 }, Element::Type::Surface), // 2 - Element({ 4, 5, 2 }, Element::Type::Surface), // 3 - - Element({ 8, 10, 9 }, Element::Type::Surface), // 4 - Element({ 11, 8, 6 }, Element::Type::Surface), // 5 - Element({ 8, 7, 6 }, Element::Type::Surface), // 6 - Element({ 11, 10, 8 }, Element::Type::Surface), // 7 - - Element({ 8, 2, 7 }, Element::Type::Surface), // 8 - Element({ 1, 7, 2 }, Element::Type::Surface), // 9 - Element({ 10, 4, 9 }, Element::Type::Surface), // 10 - Element({ 9, 4, 3 }, Element::Type::Surface), // 11 - - Element({ 6, 5, 11 }, Element::Type::Surface), // 12 - Element({ 5, 6, 0 }, Element::Type::Surface), // 13 - - - Element({ 7, 1, 6 }, Element::Type::Surface), // 14 - Element({ 6, 1, 0 }, Element::Type::Surface), // 15 - Element({ 9, 3, 8 }, Element::Type::Surface), // 16 - Element({ 8, 3, 2 }, Element::Type::Surface), // 17 - - Element({ 4, 11, 5 }, Element::Type::Surface), // 18 - Element({ 11, 4, 10 }, Element::Type::Surface), // 19 + Element({ 5, 2, 6 }, Element::Type::Surface), + Element({ 3, 0, 2 }, Element::Type::Surface), + Element({ 4, 6, 2 }, Element::Type::Surface), + Element({ 2, 0, 1 }, Element::Type::Surface), + Element({ 5, 3, 2 }, Element::Type::Surface), + Element({ 7, 9, 8 }, Element::Type::Surface), + Element({ 9, 7, 11 }, Element::Type::Surface), + Element({ 9, 11, 10 }, Element::Type::Surface), + Element({ 7, 12, 11 }, Element::Type::Surface), + Element({ 1, 8, 9 }, Element::Type::Surface), + Element({ 1, 9, 2 }, Element::Type::Surface), + Element({ 4, 10, 11 }, Element::Type::Surface), + Element({ 4, 11, 6 }, Element::Type::Surface), + Element({ 0, 3, 7 }, Element::Type::Surface), + Element({ 3, 5, 7 }, Element::Type::Surface), + Element({ 5, 12, 7 }, Element::Type::Surface), + Element({ 0, 7, 1 }, Element::Type::Surface), + Element({ 1, 7, 8 }, Element::Type::Surface), + Element({ 2, 9, 4 }, Element::Type::Surface), + Element({ 4, 9, 10 }, Element::Type::Surface), + Element({ 6, 11, 12 }, Element::Type::Surface), + Element({ 6, 12, 5 }, Element::Type::Surface), }; Mesh result = Smoother(mesh, smootherOpts).getMesh(); - assertCoordinatesListEquals(expectedCoordinates, result.coordinates); assertElementsListEquals(expectedElements, result.groups[0].elements); }