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/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 72% rename from src/cgal/Delaunator.h rename to src/cgal/DelaunatorCGAL.h index 7aac972..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, @@ -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/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/CMakeLists.txt b/src/core/CMakeLists.txt index d481f43..d1e6c0e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -7,6 +7,8 @@ add_library(tessellator-core "Smoother.cpp" "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/Collapser.cpp b/src/core/Collapser.cpp index 6a2e8c4..787f4cf 100644 --- a/src/core/Collapser.cpp +++ b/src/core/Collapser.cpp @@ -4,8 +4,6 @@ #include "utils/RedundancyCleaner.h" #include "utils/MeshTools.h" -#include "Collapser.h" - namespace meshlib { namespace core { @@ -13,6 +11,7 @@ using namespace utils; Collapser::Collapser(const Mesh& in, int decimalPlaces, const std::vector& dimensionPolicy) { + if (dimensionPolicy.size() == 0) { dimensionPolicy_ = std::vector(in.groups.size(), Element::Type::Surface); } @@ -21,16 +20,19 @@ 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,11 +85,18 @@ 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); + redundancyCleaner::fuseCoords(mesh); + redundancyCleaner::cleanCoords(mesh); for (auto & group : mesh.groups) { for (auto& element : group.elements) { diff --git a/src/core/Delaunator.cpp b/src/core/Delaunator.cpp new file mode 100644 index 0000000..706a279 --- /dev/null +++ b/src/core/Delaunator.cpp @@ -0,0 +1,136 @@ +#include "Delaunator.h" +#include "utils/Geometry.h" +#include "cgal/LSFPlane.h" + +namespace meshlib::core { + +Delaunator::Delaunator(const Coordinates& globalCoordinates) : globalCoordinates_(&globalCoordinates) {} + +Elements Delaunator::mesh(const Polygons& constrainingPolygons, bool ignoreCoplanarity) const { + try { + IdSet targetVertices; + + for (auto& polygon : constrainingPolygons) { + targetVertices.insert(polygon.begin(), polygon.end()); + } + + if (!ignoreCoplanarity) { + checkConstraintsArePlanar(targetVertices); + } + PointToId 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( + PointToId& pointsToIds, + const IdSet& targetVertices, + const Polygons& constrainingPolygons) const { + Triangulation cdt; + + Coordinates rotatedCoordinates; + std::vector originalIds; + for (auto const& id : targetVertices) { + rotatedCoordinates.push_back((*globalCoordinates_)[id]); + originalIds.push_back(id); + } + + VecD normal = cgal::LSFPlane(rotatedCoordinates.begin(), rotatedCoordinates.end()).getNormal(); + + 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(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; + 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); + + std::vector edges; + edges.reserve(targetVertices.size()); + + for (const auto& polygon : constrainingPolygons) { + for (std::size_t i = 0; i < polygon.size(); ++i) { + 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(leftId, rightId); + } + } + + cdt.insertEdges(edges); + + return cdt; +} + +Elements Delaunator::convertFromCDT(const Triangulation& cdt, const PointToId& 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) { + const Point& point = cdt.vertices[id]; + vertices.push_back(pointToId.left.at(point)); + } + + 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..3903e0d --- /dev/null +++ b/src/core/Delaunator.h @@ -0,0 +1,50 @@ +#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(), bool ignoreCoplanarity = false) const; + + +private: + const Coordinates* globalCoordinates_ = nullptr; + + typedef CDT::Triangulation Triangulation; + typedef CDT::V2d Point; + typedef CDT::VertInd PointId; + + 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(PointToId& pointToId, const IdSet& targetVertices, const Polygons& constrainingPolygons) const; + Elements convertFromCDT(const Triangulation& cdt, const PointToId& pointToId) const; +}; + +} +} diff --git a/src/core/Slicer.cpp b/src/core/Slicer.cpp index fb5d7a2..111a886 100644 --- a/src/core/Slicer.cpp +++ b/src/core/Slicer.cpp @@ -96,9 +96,9 @@ 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_); + 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_); @@ -128,6 +128,11 @@ Elements Slicer::sliceTriangle( auto n{ utils::Geometry::normal(tri) }; auto path = utils::ConvexHull(&sCoords).get(vIds, n); Elements newTris = buildTrianglesFromPath(sCoords, path); + 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; diff --git a/src/core/Slicer.h b/src/core/Slicer.h index e0bae73..e4602e0 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 = 2; }; class Slicer : public utils::GridTools { diff --git a/src/core/Smoother.cpp b/src/core/Smoother.cpp index 9a15452..07a0193 100644 --- a/src/core/Smoother.cpp +++ b/src/core/Smoother.cpp @@ -21,22 +21,24 @@ using namespace meshTools; Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : - sT_(SmootherTools(mesh.grid)), opts_(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) { - 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_.buildCellElemMap(g.elements, mesh_.coordinates)) { + 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); @@ -44,11 +46,11 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : } 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( @@ -56,7 +58,7 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : 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( @@ -64,35 +66,34 @@ Smoother::Smoother(const Mesh& mesh, const SmootherOptions& opts) : 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); + + redundancyCleaner::fuseCoords(res); + redundancyCleaner::removeDegenerateElements(res); res = buildMeshFilteringElements(res, isTriangle); - RedundancyCleaner::cleanCoords(res); + 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::fuseCoords(mesh_); + redundancyCleaner::removeDegenerateElements(mesh_); + redundancyCleaner::cleanCoords(mesh_); meshTools::checkNoCellsAreCrossed(mesh_); } } -} \ No newline at end of file +} 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; diff --git a/src/core/SmootherTools.cpp b/src/core/SmootherTools.cpp index fd38246..29dd5aa 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( @@ -143,7 +143,7 @@ SmootherTools::SingularIds SmootherTools::buildSingularIds( contourIds = CoordGraph(elems).getBoundaryGraph().getVertices(); - for (auto const& c : buildCellElemMap(elems, coords)) { + for (auto const& c : buildCellElemMapStrict(elems, coords)) { const std::vector graphs = CoordGraph::buildFromElementsViews( Geometry::buildDisjointSmoothSets(c.second, coords, smoothSetAngle)); @@ -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(), @@ -201,7 +224,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])) { @@ -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( @@ -330,17 +355,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)) { @@ -472,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]; + } } } @@ -498,6 +546,110 @@ void SmootherTools::collapseInteriorPointsToBound( updateCoordinates(coords, toMove); } +void SmootherTools::collapsePointsOnContourWithDelanautor( + Elements& elems, + Coordinates& coords, + const ElementsView& patch, + const SingularIds& sIds) { + + CoordGraph g(patch); + + auto cPolygons = g.getBoundaryGraph().findCycles(); + std::vector newCPolygons; + + /* + std::map neighbours; + /**/ + newCPolygons.reserve(cPolygons.size()); + // Important: FeatureIds, + for (const auto& cycle : cPolygons) { + CoordinateIds newCycle; + + if (cycle.size() < 3) { + continue; + } + + 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 (filteredRepeated.size() < 3) { + continue; + } + Relative center; + for (auto coordId : filteredRepeated) { + center += coords[coordId]; + } + center /= filteredRepeated.size(); + + bool isCycleOnSameFace = true; + std::size_t i = 0; + while (isCycleOnSameFace && i < filteredRepeated.size()) { + isCycleOnSameFace = areCoordOnSameFace(center, coords[filteredRepeated[i]]); + ++i; + } + + 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 (newCycle.size() < 3) { + newCycle.clear(); + + newCycle.insert(newCycle.begin(), filteredRepeated.begin(), filteredRepeated.end()); + } + + newCPolygons.push_back(newCycle); + } + + if (newCPolygons.size() == 0) { + return; + } + + 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[i], 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 973de4f..b5dfdc1 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, @@ -72,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, @@ -82,7 +90,13 @@ class SmootherTools : public utils::GridTools { const Coordinates& cs, const ElementsView& patch); + static bool patchIsPlanar( + const Coordinates& cs, + const ElementsView& patch); + private: + Delaunator delaunator_; + std::mutex writingCoordinates_; std::mutex writingElements_; @@ -128,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/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/src/core/Staircaser.cpp b/src/core/Staircaser.cpp index 40c9d00..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; @@ -41,28 +41,28 @@ Mesh Staircaser::getMesh() } } - RedundancyCleaner::fuseCoords(mesh_); - RedundancyCleaner::removeDegenerateElements(mesh_); - RedundancyCleaner::cleanCoords(mesh_); + redundancyCleaner::fuseCoords(mesh_); + redundancyCleaner::removeDegenerateElements(mesh_); + redundancyCleaner::cleanCoords(mesh_); 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,67 +74,68 @@ 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]); + } } } } - for (const auto& c : cellsCoord2) { - auto it = cellElemMap.find(c); + for (const auto& cell : cellsCoord2) { + auto it = cellElemMap.find(cell); if (it == cellElemMap.end()) { continue; } else { - for (const auto& e : cellElemMap.at(c)) { + for (const auto& e : cellElemMap.at(cell)) { const auto& elementVertices = e->vertices; 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]); } } } } - for (const auto& v : neighborsOfVertex1) { - if (neighborsOfVertex2.count(v)) { - commonNeighborsVertices.insert(v); + for (const auto& vertexId : neighborsOfVertex1) { + if (neighborsOfVertex2.count(vertexId)) { + commonNeighborsVertices.insert(vertexId); } } return commonNeighborsVertices; } -Elements findTrianglesWithEdge(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,19 +174,19 @@ 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); 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]); @@ -197,24 +198,24 @@ 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); - + } } } - - 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; - 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,145 +225,128 @@ 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 it1 = std::find(verts.begin(), verts.end(), v1); - auto it2 = 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; - } - } + 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::removeDegenerateElements(mesh_); + redundancyCleaner::removeElements(mesh_, toRemove); + redundancyCleaner::removeDegenerateElements(mesh_); } std::pair Staircaser::obtainNewIndexForElement(const Element& e, const std::set& cellsToStructure, CoordinateMap& coordinateMap) { @@ -373,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)) { @@ -387,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; } @@ -417,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) { @@ -464,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); } } } @@ -492,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; } @@ -504,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; } @@ -516,50 +502,52 @@ 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()); + if(shareExactlyTwo && otherElementInCell->isTriangle()) { + + 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); - correctOrientation = (itV1 < 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 }; - - auto itOtherElement = std::find(meshGroup.elements.begin(), meshGroup.elements.end(), *otherElementsInCell); + triangle2.vertices = { vertexId4, vertexId1, vertexId3 }; + + 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); @@ -568,14 +556,16 @@ 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; break; } } - if(processed) {continue;} + if(processed) { + continue; + } } } @@ -605,9 +595,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/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/src/meshers/ConformalMesher.cpp b/src/meshers/ConformalMesher.cpp index dca455c..61eda4e 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,66 @@ 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; + } + 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] }); + + /* + + 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] }); + + /* + + for (const auto& coordinate : triv1) { + cellsWithOverlap.insert(gridTools.toCell(coordinate)); + } + for (const auto& coordinate : triv2) { + cellsWithOverlap.insert(gridTools.toCell(coordinate)); + } + /**/ + } + } + return cellsWithOverlap; +} + std::set ConformalMesher::cellsWithAVertexInAnEdgeForbiddenRegion(const Mesh& mesh) { std::set res; @@ -156,6 +217,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)); @@ -172,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); @@ -197,21 +302,33 @@ 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); + /* + 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); + // nonConformalCells = findNonConformalCells(res); log("Non-conformal cells found after Selective Structuring: " + std::to_string(nonConformalCells.size()), 1); // 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/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/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/ElemGraph.cpp b/src/utils/ElemGraph.cpp index 7ac84ed..1db1a9d 100644 --- a/src/utils/ElemGraph.cpp +++ b/src/utils/ElemGraph.cpp @@ -251,15 +251,136 @@ 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.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); + } } } diff --git a/src/utils/Geometry.cpp b/src/utils/Geometry.cpp index fe2fc4e..ab04fd5 100644 --- a/src/utils/Geometry.cpp +++ b/src/utils/Geometry.cpp @@ -172,13 +172,111 @@ 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 + // + // + // + // 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))) + // + // + // + + + + throw std::runtime_error("Not implemented"); VecD res({ 0.0, 0.0, 0.0 }); return res / res.norm(); diff --git a/src/utils/Geometry.h b/src/utils/Geometry.h index 113593b..e17d396 100644 --- a/src/utils/Geometry.h +++ b/src/utils/Geometry.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Types.h" @@ -61,7 +62,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 +85,7 @@ class Geometry { - a2 * (b1 * c3 - b3 * c1) + a3 * (b1 * c2 - b2 * c1); - const bool isCoplanar{ det < COPLANARITY_TOLERANCE }; + const bool isCoplanar{ std::abs(det) < coplanarTolerance }; if (!isCoplanar) { return false; @@ -95,13 +97,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/GridTools.cpp b/src/utils/GridTools.cpp index 2e282ad..85dd5e3 100644 --- a/src/utils/GridTools.cpp +++ b/src/utils/GridTools.cpp @@ -505,14 +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; 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++; } } @@ -526,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++; } } @@ -712,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; } @@ -810,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()); @@ -824,6 +844,59 @@ 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::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) { + const auto touchingIt = coordTouchingMap.find(v); + + if (touchingIt != coordTouchingMap.end()) { + allTouching.insert(touchingIt->second.begin(), touchingIt->second.end()); + } + else { + std::set touching = getTouchingCells(coords[v]); + 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 35e4d9f..a20bb0e 100644 --- a/src/utils/GridTools.h +++ b/src/utils/GridTools.h @@ -97,6 +97,12 @@ 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; std::map> buildCellCoordMap( std::vector& coords) const; std::map> buildCellTriMap( diff --git a/src/utils/MeshTools.cpp b/src/utils/MeshTools.cpp index b2a25f4..7e79169 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 @@ -180,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++) { @@ -194,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; } @@ -418,4 +419,125 @@ 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; + } + } + } + } + + redundancyCleaner::removeDegenerateElements(resultMesh); + redundancyCleaner::fuseCoords(resultMesh); + redundancyCleaner::cleanCoords(resultMesh); + + 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/src/utils/RedundancyCleaner.cpp b/src/utils/RedundancyCleaner.cpp index e36161e..f2149a4 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,10 +314,14 @@ 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; + 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(); @@ -336,4 +339,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/CMakeLists.txt b/test/CMakeLists.txt index cea5caa..ab5a6b4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,10 +18,12 @@ 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" "core/SmootherToolsTest.cpp" + "core/SplicerTest.cpp" "core/StaircaserTest.cpp" "types/MeshTest.cpp" "utils/ConvexHullTest.cpp" @@ -50,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 new file mode 100644 index 0000000..5943cbf --- /dev/null +++ b/test/core/DelaunatorTest.cpp @@ -0,0 +1,261 @@ +#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}), // 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 + }; + } + + 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_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()); + } +} +/* +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 diff --git a/test/core/SlicerTest.cpp b/test/core/SlicerTest.cpp index 1ae8c6e..b6acb79 100644 --- a/test/core/SlicerTest.cpp +++ b/test/core/SlicerTest.cpp @@ -931,9 +931,10 @@ 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); + redundancyCleaner::cleanCoords(resultMesh); ASSERT_EQ(resultMesh.coordinates.size(), expectedCoordinates.size()); @@ -994,14 +995,37 @@ 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); + + 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 228ddb0..05c12c5 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,16 +222,171 @@ 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 +/// 16=──────────────17 (16->10)─────────(17->11) +/// │ ⟍‾-_ │ │ ⟍ /│ +/// │ ⟍ ‾-_ │ │ ⟍ / │ +/// │ ⟍ ‾-_ │ │ ⟍ / │ +/// │ ⟍ ‾-_ │ -> │ ⟍ / │ +/// 15──────────13───14 (15->9)────(13->8) │ +/// _-‾ \ │ _-‾ \ │ +/// _-‾ ‾\│ _-‾ ‾\│ +/// 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) +/// │ _-‾ │ │ ⟋ │ +/// │ __-‾ │ │ ⟋ │ +/// │ _-‾ │ │ ⟋ │ +/// │ _-‾ │ -> │ ⟋ │ +/// 14=-════════════4,7 │ ⟋ │ +/// │ ___--‾‾ │ │ ⟋ │ +/// │ __--‾‾____----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) +/// │ ⟍ │ │ ⟋ ⎸ +/// │ ⟍ │ │ ⟋ │ +/// │ ⟍ │ │ ⟋ │ +/// │ ⟍ │ -> │ ⟋ │ +/// │ ⟍ │ │ ⟋ │ +/// │ ⟍ │ │ ⟋ │ +/// │ ⟍ ⎸ │ ⟋ │ +/// x 16───────────────17 x (16->10)─────────(17->11) +/// y y +/// + +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 }), + 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({ 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); +} + 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); + 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; @@ -108,12 +398,17 @@ 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(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); + vtkIO::exportMeshToVTU("testData/cases/alhambra/alhambra.contour.vtk", contourMesh); } diff --git a/test/core/SmootherToolsTest.cpp b/test/core/SmootherToolsTest.cpp index 78e2670..bb45869 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 @@ -434,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)); @@ -461,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)); @@ -491,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; @@ -514,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; @@ -543,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)); @@ -568,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)); @@ -589,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); @@ -611,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); @@ -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, mesh.coordinates); + 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 @@ -648,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()); @@ -663,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]}); @@ -679,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); @@ -729,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)) { @@ -782,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; @@ -807,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)); @@ -817,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)); @@ -851,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)); 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 diff --git a/test/core/StaircaserTest.cpp b/test/core/StaircaserTest.cpp index 4975e5f..ab9c491 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,175 +1343,149 @@ 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} 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 - // | / ⟍ | ⫽ ⎸ / ᐱ ║//⫽ | / \ ⎸ ⫽ | / ᐱ ║///⫽ - // | / ⟍ | ⫽ ⎸ / ⎹ ║/⫽ / ⎹ / \ | ⫽ ⎹ / ⎹ ║//⫽ / - // |/ ⟍ |⫽ ⎸/ ║⫽ ⩗ |/ \⎸⫽ |/ ║⫽ ⩗ + // 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 + // | / ⟍ | ⫽ ⎹ / ᐱ ║///⫽ ⎹ / \ ⎸ ⫽ | / ᐱ ║////⫽ + // | / ⟍ | ⫽ ⎹ / ⎹ ⎹⎸/⫽ / ⎸ / \ | ⫽ ⎹ / ⎹ ⎹⎸//⫽ / + // |/ ⟍ |⫽ ⎹/ ⎹⎸⫽ ⩗ ⎹/ \⫽ |/ ║/⫽ ⩗ // *--------------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; @@ -1876,152 +1624,87 @@ 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} - // /| |\ /| / ⫽\\\\\\\\\\\\\⫽/║ - // / | ⎹ \ / | ⩗ ⫽\\\\\\\\\\\\\⫽//║ - // z/ | ⎹ \ / | z⫽\\\\\\\\\\\\\⫽///║⎹ - // *---┼---┼{0}--* | -> {2.5->5}=========={0}//║ v - // | |y ⎹/ | | ⎸ ║//////////|///║ - // | *--{1}----┼---* {1->3}=========╪=={0.66->2} - // | / | / ⎸ / <- | / - // | / | / ⎸ / | / - // |/ |/ ⎸/ |/ + // T0 *-{2}---------* {2->4}═════════{0.33->1} + // /| |\ /| / ⫽\\\\\\\\\\\\\⫽/║ + // / | ⎹ \ / | ⩗⫽\\\\\\\\\\\\\⫽//⎹⎸ + // 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 ║\\\\\\\\\\\\\\║\⫽ / + // T1 *--------{1}--* {2->4}══════════{1->3} + // /| ⟋ / /⎸ / ⫽\\\\\\\\\\\\\\⫽║ + // / | ⟋ / / ⎸ ⩗ ⫽\\\\\\\\\\\\\⫽\⎹⎸ + // z/ ⟋ / / ⎸ z⫽\\\\\\\\\\\\\⫽\\\║ ᐱ + // *{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}-------------* - // {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} // | / ⟍ \ ⎸ / ║\\\\\\\\\\\\\\\║///⫽ // | / ⟍ \ ⎸ / ║\\\\\\\\\\\\\\\║//⫽ / // | / ⟍ \ ⎸ / ║\\\\\\\\\\\\\\\║/⫽ ⩗ // |/ ⟍\⎸/ ║\\\\\\\\\\\\\\\║⫽ - // *--------------{0} x {6}============={0} x + // *--------------{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}-----┼--/ ║\\\\\\\\\\\\\\\║//⫽ / // | / | / ║\\\\\\\\\\\\\\\║/⫽ ⩗ // |/ |/ ║\\\\\\\\\\\\\\\║⫽ - // *-------------- * 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} ⎹ - // | 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; @@ -2096,43 +1779,16 @@ 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); } } -TEST_F(StaircaserTest, selectiveStructurerWithEmptySetOfCells) +TEST_F(StaircaserTest, selectiveStaircaserWithEmptySetOfCells) { // *-------------*-------------* *-------------*-------------* @@ -2141,7 +1797,7 @@ TEST_F(StaircaserTest, selectiveStructurerWithEmptySetOfCells) // | | _2 | -> | | _2 | // | | _-‾ | | | _-‾ | // | | _-‾ | | | _-‾ | - // | 0-------1-‾ | | 0-------1-‾ | + // | 0───────1-‾ | | 0───────1-‾ | // *-------------*-------------* *-------------*-------------* // @@ -2167,41 +1823,16 @@ 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.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]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); } @@ -2214,8 +1845,8 @@ TEST_F(StaircaserTest, modifyCoordinateOfASpecificCell) // | | _2 | -> | | _2 | // | | _-‾ | | | _-‾ | // | | _-‾ | | | _-‾ | - // | 0-------1-‾ | | | _-‾ | - // *-------------*-------------* 0===============1‾--------------* + // | 0───────1-‾ | | | _-‾ | + // *-------------*-------------* 0═══════════════1=--------------* // float lowerCoordinateValue = -5.0; @@ -2254,47 +1885,29 @@ 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]); - } - } + 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-‾ | | | _-‾ | + // *-------------*-------------* 0═══════════════1‾--------------* // float lowerCoordinateValue = -5.0; @@ -2342,42 +1955,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]; - - 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) +TEST_F(StaircaserTest, verifyOrderInSelectiveStaircaser) { - // *-------------*-------------* (4->3)==========(3->2)-------------* - // | 4-------3-_ | | | ‾-_ | + // *-------------*-------------* (4->3)══════════(3->2)-------------* + // | 4───────3-_ | | | ‾-_ | // | | ‾-_ | | | ‾-_ | // | | ‾--_2 | -> | | ‾(2->4) | // | | _-‾ | | | _- | // | | _-‾ | | | _-‾ | - // | 0-------1-‾ | | | _-‾ | - // *-------------*-------------* 0===============1‾--------------* + // | 0───────1-‾ | | | _-‾ | + // *-------------*-------------* 0═══════════════1‾--------------* // float lowerCoordinateValue = -5.0; @@ -2424,41 +2017,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]; - 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) +TEST_F(StaircaserTest, staircaseSpecificTriangles) { - // *-------------*-------------* (4->3)==========(3->2)-------------* + // *-------------*-------------* (4->3)══════════(3->2)-------------* // | 4-------3-_ | ║///////////////║ ‾-_ | - // | | /║ ‾-_ | ║///////////////║ ‾-_ | - // | | / ║ ‾--_2 | -> ║///////////////║ ‾(2->4) | - // | | / ║ _-‾ | ║///////////////║ _- | - // | |/ ║ _-‾ | ║///////////////║ _-‾ | - // | 0-------1-‾ | ║///////////////║ _-‾ | - // *-------------*-------------* 0===============1‾--------------* + // | │ /║ ‾-_ | ║///////////////║ ‾-_ | + // | │ / ║ ‾--_2 | -> ║///////////////║ ‾(2->4) | + // | │ / ║ _-‾ | ║///////////////║ _- | + // | │/ ║ _-‾ | ║///////////////║ _-‾ | + // | 0───────1-‾ | ║///////////////║ _-‾ | + // *-------------*-------------* 0═══════════════1‾--------------* // float lowerCoordinateValue = -5.0; @@ -2506,48 +2080,28 @@ 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]; - 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) +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; @@ -2605,55 +2159,29 @@ 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]; - - 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) +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; @@ -2710,54 +2238,28 @@ 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]; - - 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) +TEST_F(StaircaserTest, selectiveStaircaser_SplitLinesWithNeighborTriangle) { - // *----0========2=============5 *---(0->3)==(2->5)========(5->6) - // | ‾-_ ║|\ ║ | ‾-_ ║ \ ║ - // | ‾-_ ║ | \ ║ | ‾-_ ║ \ ║ - // | - ║ | \ ║ | - ║ \ ║ - // | ‾1 | \ ║ | (1->4) \ ║ - // | |‾- | \ ║ | ║‾‾--__ \ ║ - // | | ‾-_| \ ║ | ║ ‾‾--_\ ║ - // *-------------*-----3=======4 -> *-----------(3->0)========(4->1) - // | | \ | | | ║ - // | | \ | | | ║ - // | | \ | | | ║ - // | | \ | | | ║ - // | | \ | | | ║ - // | | \ | | | ║ + // *----0════════2═════════════5 *---(0->3)══(2->5)════════(5->6) + // | ‾-_ ║\\ ║ | ‾-_ ║ \ ║ + // | ‾-_ ║ \ \ ║ | ‾-_ ║ \ ║ + // | - ║ \ \ ║ | - ║ \ ║ + // | ‾1 \ \ ║ | (1->4) \ ║ + // | |‾- \ \ ║ | ║‾‾--__ \ ║ + // | | ‾-_\ \ ║ | ║ ‾‾--_\ ║ + // *-------------*-----3═══════4 -> *-----------(3->0)════════(4->1) + // | | \ ║ | | ║ + // | | \ ║ | | ║ + // | | \ ║ | | ║ + // | | \ ║ | | ║ + // | | \ ║ | | ║ + // | | \ ║ | | ║ // *-------------*-------------6 *-------------*-----------(6->2) float lowerCoordinateValue = -5.0; @@ -2813,33 +2315,324 @@ 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]); - } - } + assertCoordinatesListEquals(expectedRelatives, resultMesh.coordinates); + assertElementsListEquals(expectedElements, resultMesh.groups[0].elements); +} - 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()); +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); - 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()); + std::set cellSet; + cellSet.insert(Cell({ 1, 0, 1 })); - for (std::size_t e = 0; e < expectedElements.size(); ++e) { - auto& resultElement = resultMesh.groups[0].elements[e]; - auto& expectedElement = expectedElements[e]; + 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 + }; - for (std::size_t v = 0; v < expectedElement.vertices.size(); ++v) { - EXPECT_EQ(resultElement.vertices[v], expectedElement.vertices[v]); - } - } - + 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 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); +} + +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 bbc52a7..3ad4b3a 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" @@ -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(); @@ -333,6 +333,275 @@ 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); + + 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) +{ + // 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()); + + 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) +{ + // 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()); + + 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) +{ + // 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.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, 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) +{ + // 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 @@ -365,17 +634,36 @@ 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, 61); - inputMesh.grid[Y] = utils::GridTools::linspace(-60.0, 60.0, 61); + /**/ + 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[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); @@ -389,6 +677,7 @@ TEST_F(ConformalMesherTest, alhambra) utils::meshTools::convertToAbsoluteCoordinates(dbgMesh); exportMeshToVTU("testData/cases/alhambra/alhambra.breaksRuleNo2.vtk", dbgMesh); } + /**/ } TEST_F(ConformalMesherTest, cone) @@ -475,6 +764,4 @@ TEST_F(ConformalMesherTest, thinCylinder) // EXPECT_EQ(4, countMeshElementsIf(p, isTriangle)); // } - - } \ 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/GridToolsTest.cpp b/test/utils/GridToolsTest.cpp index 90d1eb1..94da874 100644 --- a/test/utils/GridToolsTest.cpp +++ b/test/utils/GridToolsTest.cpp @@ -219,6 +219,683 @@ 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 | + // | *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 + // *-----------------* | *-----------------* + // | | | | | + // | | | | | + // 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)); + } + + // + // 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 }; + 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_TRUE(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)); + } + + // + // 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) { Grid grid; 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 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()); 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": {