From 34ebcbc8e3a5cb24315ff2d80b155ef4f30eecdd Mon Sep 17 00:00:00 2001 From: Grant Karapetyan Date: Wed, 18 Sep 2024 18:29:56 +0300 Subject: [PATCH 1/3] Other way to get valid hole contours in flat filling --- source/MRMesh/MR2DContoursTriangulation.cpp | 58 ++++++++++++++++++--- source/MRMesh/MR2DContoursTriangulation.h | 5 +- source/MRMesh/MRFillContours2D.cpp | 25 +++++---- 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/source/MRMesh/MR2DContoursTriangulation.cpp b/source/MRMesh/MR2DContoursTriangulation.cpp index 86339aa1e6d1..010d9e5ffe4e 100644 --- a/source/MRMesh/MR2DContoursTriangulation.cpp +++ b/source/MRMesh/MR2DContoursTriangulation.cpp @@ -120,8 +120,9 @@ class SweepLineQueue const HolesVertIds* holesVertId = nullptr, bool abortWhenIntersect = false, WindingMode mode = WindingMode::NonZero, - bool needOutline = false // if set do not do real triangulation, just marks inside faces as present, + bool needOutline = false, // if set do not do real triangulation, just marks inside faces as present, // also does not merge same vertices + std::vector* outBoundaries = nullptr // optional out EdgePaths that corresponds to initial contours ); size_t vertSize() const { return tp_.vertSize(); } @@ -146,6 +147,8 @@ class SweepLineQueue bool needOutline_ = false; // if set fails on first found intersection bool abortWhenIntersect_ = false; + // optional out EdgePaths that corresponds to initial contours + std::vector* outBoundaries_ = nullptr; // make base mesh only containing input contours as edge loops void initMeshByContours_( const Contours2d& contours ); // merge same points on base mesh @@ -278,9 +281,11 @@ SweepLineQueue::SweepLineQueue( const HolesVertIds* holesVertId, bool abortWhenIntersect, WindingMode mode, - bool needOutline ) : + bool needOutline, + std::vector* outBoundaries ) : needOutline_{ needOutline }, abortWhenIntersect_{ abortWhenIntersect }, + outBoundaries_{ outBoundaries }, windingMode_{ mode } { Box3d box; @@ -898,16 +903,30 @@ void SweepLineQueue::initMeshByContours_( const Contours2d& contours ) } } } + + int boundId = -1; + if ( outBoundaries_ ) + outBoundaries_->resize( contours.size() ); + int firstVert = 0; for ( const auto& c : contours ) { + ++boundId; if ( c.size() <= 3 ) continue; int size = int( c.size() ) - 1; + if ( outBoundaries_ ) + ( *outBoundaries_ )[boundId].resize( size ); + for ( int i = 0; i < size; ++i ) - tp_.setOrg( tp_.makeEdge(), VertId( firstVert + i ) ); + { + auto newEdgeId = tp_.makeEdge(); + tp_.setOrg( newEdgeId, VertId( firstVert + i ) ); + if ( outBoundaries_ ) + ( *outBoundaries_ )[boundId][i] = newEdgeId; + } const auto& edgePerVert = tp_.edgePerVertex(); for ( int i = 0; i < size; ++i ) tp_.splice( edgePerVert[VertId( firstVert + i )], edgePerVert[VertId( firstVert + ( ( i + int( size ) - 1 ) % size ) )].sym() ); @@ -1037,6 +1056,31 @@ void SweepLineQueue::removeMultipleAfterMerge_() } assert( multiplesFromThis.size() > 1 ); + if ( outBoundaries_ ) + { + auto& bunds = *outBoundaries_; + auto getBoundId = [&bunds] ( EdgeId e )->std::pair + { + int i0 = 0; + auto i1 = int( e.undirected() ); + while ( i1 >= bunds[i0].size() ) + { + ++i0; + i1 -= int( bunds[i0].size() ); + } + assert( e.undirected() == bunds[i0][i1].undirected() ); + return { i0,i1 }; + }; + auto [bf0, bf1] = getBoundId( multiplesFromThis.front() ); + auto bf = bunds[bf0][bf1]; + for ( int i = 1; i < multiplesFromThis.size(); ++i ) + { + auto [bi0, bi1] = getBoundId( multiplesFromThis[i] ); + auto& bi = bunds[bi0][bi1]; + bi = multiplesFromThis[i] == bi ? bf : bf.sym(); + } + } + auto& edgeInfo = windingInfo_[multiplesFromThis.front().undirected()]; edgeInfo.windingModifier = 1; bool uniqueIsOdd = int( multiplesFromThis.front() ) & 1; @@ -1298,18 +1342,18 @@ Mesh triangulateContours( const Contours2f& contours, const HolesVertIds* holeVe return triangulateContours( contsd, holeVertsIds ); } -std::optional triangulateDisjointContours( const Contours2d& contours, const HolesVertIds* holeVertsIds /*= nullptr*/ ) +std::optional triangulateDisjointContours( const Contours2d& contours, const HolesVertIds* holeVertsIds /*= nullptr*/, std::vector* outBoundaries /*= nullptr*/ ) { if ( contours.empty() ) return Mesh(); - SweepLineQueue triangulator( contours, holeVertsIds, true ); + SweepLineQueue triangulator( contours, holeVertsIds, true, WindingMode::NonZero, false, outBoundaries ); return triangulator.run(); } -std::optional triangulateDisjointContours( const Contours2f& contours, const HolesVertIds* holeVertsIds /*= nullptr*/ ) +std::optional triangulateDisjointContours( const Contours2f& contours, const HolesVertIds* holeVertsIds /*= nullptr*/, std::vector* outBoundaries /*= nullptr*/ ) { const auto contsd = copyContours( contours ); - return triangulateDisjointContours( contsd, holeVertsIds ); + return triangulateDisjointContours( contsd, holeVertsIds, outBoundaries ); } } diff --git a/source/MRMesh/MR2DContoursTriangulation.h b/source/MRMesh/MR2DContoursTriangulation.h index 92eae7c3cc8d..a50835cd5f8b 100644 --- a/source/MRMesh/MR2DContoursTriangulation.h +++ b/source/MRMesh/MR2DContoursTriangulation.h @@ -75,10 +75,11 @@ MRMESH_API Mesh triangulateContours( const Contours2f& contours, const HolesVert * @brief triangulate 2d contours * only closed contours are allowed (first point of each contour should be the same as last point of the contour) * @param holeVertsIds if set merge only points with same vertex id, otherwise merge all points with same coordinates + * @param outBoundaries optional output EdgePaths that correspond to initial contours * @return std::optional : if some contours intersect return false, otherwise return created mesh */ -MRMESH_API std::optional triangulateDisjointContours( const Contours2d& contours, const HolesVertIds* holeVertsIds = nullptr ); -MRMESH_API std::optional triangulateDisjointContours( const Contours2f& contours, const HolesVertIds* holeVertsIds = nullptr ); +MRMESH_API std::optional triangulateDisjointContours( const Contours2d& contours, const HolesVertIds* holeVertsIds = nullptr, std::vector* outBoundaries = nullptr ); +MRMESH_API std::optional triangulateDisjointContours( const Contours2f& contours, const HolesVertIds* holeVertsIds = nullptr, std::vector* outBoundaries = nullptr ); } } \ No newline at end of file diff --git a/source/MRMesh/MRFillContours2D.cpp b/source/MRMesh/MRFillContours2D.cpp index 4e124ea97a65..faa2c52723a5 100644 --- a/source/MRMesh/MRFillContours2D.cpp +++ b/source/MRMesh/MRFillContours2D.cpp @@ -12,6 +12,7 @@ #include "MRPlane3.h" #include "MRGTest.h" #include +#include "MRMeshSave.h" namespace MR { @@ -117,8 +118,10 @@ VoidOrErrStr fillContours2D( Mesh& mesh, const std::vector& holeRepresen auto holeVertIds = std::make_unique( PlanarTriangulation::findHoleVertIdsByHoleEdges( mesh.topology, paths ) ); + + std::vector newPaths; // make patch surface - auto fillResult = PlanarTriangulation::triangulateDisjointContours( contours2f, holeVertIds.get() ); + auto fillResult = PlanarTriangulation::triangulateDisjointContours( contours2f, holeVertIds.get(), &newPaths ); holeVertIds.reset(); if ( !fillResult ) return unexpected( "Cannot triangulate contours with self-intersections" ); @@ -129,22 +132,22 @@ VoidOrErrStr fillContours2D( Mesh& mesh, const std::vector& holeRepresen for ( auto& point : patchMeshPoints ) point = planeXf( point ); - // make - auto newPaths = findLeftBoundary( patchMesh.topology ); - - // check that patch surface borders size equal original mesh borders size if ( paths.size() != newPaths.size() ) return unexpected( "Patch surface borders size different from original mesh borders size" ); - // need to rotate to min edge to be consistent with original paths (for addPartByMask) - for ( auto& newPath : newPaths ) - std::rotate( newPath.begin(), std::min_element( newPath.begin(), newPath.end() ), newPath.end() ); - std::sort( newPaths.begin(), newPaths.end(), [] ( const EdgeLoop& l, const EdgeLoop& r ) { return l[0] < r[0]; } ); - - for ( int i = 0; i < paths.size(); ++i ) + std::vector invalidHoles; + invalidHoles.reserve( newPaths.size() ); + for ( int i = int( paths.size() ) - 1; i >= 0; --i ) { if ( paths[i].size() != newPaths[i].size() ) return unexpected( "Patch surface borders size different from original mesh borders size" ); + + // invalid holes, we could either skip them or fill other way or return error + if ( newPaths[i].empty() || patchMesh.topology.right( newPaths[i].front() ) ) + { + paths.erase( paths.begin() + i ); + newPaths.erase( newPaths.begin() + i ); + } } // move patch surface border points to original position (according original mesh) From 52e55d2bd53e9bdfbfe104c75a386a12d364d869 Mon Sep 17 00:00:00 2001 From: Grant Karapetyan Date: Wed, 18 Sep 2024 19:04:28 +0300 Subject: [PATCH 2/3] fix --- source/MRMesh/MRFillContours2D.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/source/MRMesh/MRFillContours2D.cpp b/source/MRMesh/MRFillContours2D.cpp index faa2c52723a5..165d013e9c8c 100644 --- a/source/MRMesh/MRFillContours2D.cpp +++ b/source/MRMesh/MRFillContours2D.cpp @@ -13,6 +13,7 @@ #include "MRGTest.h" #include #include "MRMeshSave.h" +#include "MRFillContour.h" namespace MR { @@ -135,19 +136,29 @@ VoidOrErrStr fillContours2D( Mesh& mesh, const std::vector& holeRepresen if ( paths.size() != newPaths.size() ) return unexpected( "Patch surface borders size different from original mesh borders size" ); - std::vector invalidHoles; - invalidHoles.reserve( newPaths.size() ); - for ( int i = int( paths.size() ) - 1; i >= 0; --i ) + std::vector invertedHoles; + invertedHoles.reserve( newPaths.size() ); + for ( int i = 0; i < paths.size(); ++i ) { if ( paths[i].size() != newPaths[i].size() ) return unexpected( "Patch surface borders size different from original mesh borders size" ); - // invalid holes, we could either skip them or fill other way or return error + // degenerate holes might invert sometimes (it is expected as far as planar triangulation does not now about input topology) if ( newPaths[i].empty() || patchMesh.topology.right( newPaths[i].front() ) ) - { - paths.erase( paths.begin() + i ); - newPaths.erase( newPaths.begin() + i ); - } + if ( !newPaths[i].empty() ) + MR::reverse( invertedHoles.emplace_back( newPaths[i] ) ); + } + if ( !invertedHoles.empty() ) + { + auto invertedParts = fillContourLeft( patchMesh.topology, invertedHoles ); + auto invertedEdges = getIncidentEdges( patchMesh.topology, invertedParts ); + patchMesh.topology.flipOrientation( &invertedEdges ); + + // validate one more time + for ( int i = 0; i < paths.size(); ++i ) + if ( newPaths[i].empty() || patchMesh.topology.right( newPaths[i].front() ) ) + if ( !newPaths[i].empty() ) + return unexpected( "Patch surface borders are incompatible with mesh borders" ); } // move patch surface border points to original position (according original mesh) From 7a2f0dff08edd1d29977e2fd9301523404ef13f9 Mon Sep 17 00:00:00 2001 From: Grant Karapetyan Date: Wed, 18 Sep 2024 19:28:19 +0300 Subject: [PATCH 3/3] fix typo --- source/MRMesh/MR2DContoursTriangulation.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/MRMesh/MR2DContoursTriangulation.cpp b/source/MRMesh/MR2DContoursTriangulation.cpp index 010d9e5ffe4e..31e166900ed0 100644 --- a/source/MRMesh/MR2DContoursTriangulation.cpp +++ b/source/MRMesh/MR2DContoursTriangulation.cpp @@ -1058,25 +1058,25 @@ void SweepLineQueue::removeMultipleAfterMerge_() if ( outBoundaries_ ) { - auto& bunds = *outBoundaries_; - auto getBoundId = [&bunds] ( EdgeId e )->std::pair + auto& bounds = *outBoundaries_; + auto getBoundId = [&bounds] ( EdgeId e )->std::pair { int i0 = 0; auto i1 = int( e.undirected() ); - while ( i1 >= bunds[i0].size() ) + while ( i1 >= bounds[i0].size() ) { ++i0; - i1 -= int( bunds[i0].size() ); + i1 -= int( bounds[i0].size() ); } - assert( e.undirected() == bunds[i0][i1].undirected() ); + assert( e.undirected() == bounds[i0][i1].undirected() ); return { i0,i1 }; }; auto [bf0, bf1] = getBoundId( multiplesFromThis.front() ); - auto bf = bunds[bf0][bf1]; + auto bf = bounds[bf0][bf1]; for ( int i = 1; i < multiplesFromThis.size(); ++i ) { auto [bi0, bi1] = getBoundId( multiplesFromThis[i] ); - auto& bi = bunds[bi0][bi1]; + auto& bi = bounds[bi0][bi1]; bi = multiplesFromThis[i] == bi ? bf : bf.sym(); } }