diff --git a/source/MRMesh/MRMeshFillHole.cpp b/source/MRMesh/MRMeshFillHole.cpp index 756a800940f0..6a703f898c2c 100644 --- a/source/MRMesh/MRMeshFillHole.cpp +++ b/source/MRMesh/MRMeshFillHole.cpp @@ -587,6 +587,27 @@ void executeHoleFillPlan( Mesh & mesh, EdgeId a0, HoleFillPlan & plan, FaceBitSe assert( plan.numTris == int( fsz - fsz0 + ( f0 ? 1 : 0 ) ) ); } +bool isFillingMultipleEdgeFree( const MeshTopology & topology, const HoleFillPlan & plan ) +{ + if ( plan.items.empty() ) + return true; + + auto getVert = [&]( int code ) + { + while ( code < 0 ) + code = plan.items[ -(code+1) ].edgeCode1; + return topology.org( EdgeId( code ) ); + }; + for ( int i = 0; i < plan.items.size(); ++i ) + { + auto v1 = getVert( plan.items[i].edgeCode1 ); + auto v2 = getVert( plan.items[i].edgeCode2 ); + if ( topology.findEdge( v1, v2 ) ) + return false; + } + return true; +} + /// this class allows you to prepare fill plans for several holes with no new memory allocations on /// second and subsequent calls class HoleFillPlanner diff --git a/source/MRMesh/MRMeshFillHole.h b/source/MRMesh/MRMeshFillHole.h index fdae2f4922a2..132d0f406e7d 100644 --- a/source/MRMesh/MRMeshFillHole.h +++ b/source/MRMesh/MRMeshFillHole.h @@ -173,23 +173,28 @@ struct HoleFillPlan /// prepares the plan how to triangulate the face or hole to the left of (e) (not filling it immediately), /// several getHoleFillPlan can work in parallel -MRMESH_API HoleFillPlan getHoleFillPlan( const Mesh& mesh, EdgeId e, const FillHoleParams& params = {} ); +[[nodiscard]] MRMESH_API HoleFillPlan getHoleFillPlan( const Mesh& mesh, EdgeId e, const FillHoleParams& params = {} ); /// prepares the plans how to triangulate the faces or holes, each given by a boundary edge (with filling target to the left), /// the plans are prepared in parallel with minimal memory allocation compared to manual calling of several getHoleFillPlan(), but it can inefficient when some holes are very complex -MRMESH_API std::vector getHoleFillPlans( const Mesh& mesh, const std::vector& holeRepresentativeEdges, const FillHoleParams& params = {} ); +[[nodiscard]] MRMESH_API std::vector getHoleFillPlans( const Mesh& mesh, const std::vector& holeRepresentativeEdges, const FillHoleParams& params = {} ); /// prepares the plan how to triangulate the planar face or planar hole to the left of (e) (not filling it immediately), /// several getPlanarHoleFillPlan can work in parallel -MRMESH_API HoleFillPlan getPlanarHoleFillPlan( const Mesh& mesh, EdgeId e ); +[[nodiscard]] MRMESH_API HoleFillPlan getPlanarHoleFillPlan( const Mesh& mesh, EdgeId e ); /// prepares the plans how to triangulate the planar faces or holes, each given by a boundary edge (with filling target to the left), /// the plans are prepared in parallel with minimal memory allocation compared to manual calling of several getPlanarHoleFillPlan(), but it can inefficient when some holes are very complex -MRMESH_API std::vector getPlanarHoleFillPlans( const Mesh& mesh, const std::vector& holeRepresentativeEdges ); +[[nodiscard]] MRMESH_API std::vector getPlanarHoleFillPlans( const Mesh& mesh, const std::vector& holeRepresentativeEdges ); /// quickly triangulates the face or hole to the left of (e) given the plan (quickly compared to fillHole function) MRMESH_API void executeHoleFillPlan( Mesh & mesh, EdgeId a0, HoleFillPlan & plan, FaceBitSet * outNewFaces = nullptr ); +/// returns true if executeHoleFillPlan() with the same topology and plan +/// does not introduce any edge with the same end-vertices as any existed edge in the mesh; +/// note: this function can be used for checking a fill plan that was generated before filling other holes +[[nodiscard]] MRMESH_API bool isFillingMultipleEdgeFree( const MeshTopology & topology, const HoleFillPlan & plan ); + /** \brief Triangulates face of hole in mesh trivially\n * \ingroup FillHoleGroup * diff --git a/source/MRTest/MRFillHoleTests.cpp b/source/MRTest/MRFillHoleTests.cpp index a3fe7621aead..5ba620cade07 100644 --- a/source/MRTest/MRFillHoleTests.cpp +++ b/source/MRTest/MRFillHoleTests.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include namespace MR @@ -117,4 +118,78 @@ TEST( MRMesh, makeBridgeEdge ) EXPECT_FALSE( x.valid() ); } +TEST( MRMesh, HoleFillPlan3 ) +{ + Mesh mesh; + const auto e = mesh.addSeparateEdgeLoop + ( { + { 0, -1, 0 }, + { 2, 0, 0 }, + { 0, 1, 0 } + } ); + + auto p0 = getPlanarHoleFillPlan( mesh, e ); + EXPECT_EQ( p0.items.size(), 0 ); + EXPECT_EQ( p0.numTris, 1 ); + + auto p1 = getPlanarHoleFillPlan( mesh, e.sym() ); + EXPECT_EQ( p1.items.size(), 0 ); + EXPECT_EQ( p1.numTris, 1 ); + + EXPECT_TRUE( isFillingMultipleEdgeFree( mesh.topology, p0 ) ); + executeHoleFillPlan( mesh, e, p0 ); + EXPECT_EQ( mesh.topology.numValidFaces(), 1 ); + EXPECT_FALSE( mesh.topology.isClosed() ); + + EXPECT_TRUE( isFillingMultipleEdgeFree( mesh.topology, p1 ) ); + executeHoleFillPlan( mesh, e.sym(), p1 ); + EXPECT_EQ( mesh.topology.numValidFaces(), 2 ); + EXPECT_TRUE( mesh.topology.isClosed() ); +} + +TEST( MRMesh, HoleFillPlan4 ) +{ + Mesh mesh; + const auto e = mesh.addSeparateEdgeLoop + ( { + { 0, -1, 0 }, + { 2, 0, 0 }, + { 0, 1, 0 }, + { -2, 0, 0 } + } ); + + auto p0 = getPlanarHoleFillPlan( mesh, e ); + EXPECT_EQ( p0.items.size(), 1 ); + EXPECT_EQ( p0.numTris, 2 ); + + auto p1 = getPlanarHoleFillPlan( mesh, e.sym() ); + EXPECT_EQ( p1.items.size(), 1 ); + EXPECT_EQ( p1.numTris, 2 ); + + EXPECT_TRUE( isFillingMultipleEdgeFree( mesh.topology, p0 ) ); + executeHoleFillPlan( mesh, e, p0 ); + EXPECT_EQ( mesh.topology.numValidFaces(), 2 ); + EXPECT_FALSE( mesh.topology.isClosed() ); + EXPECT_FALSE( hasMultipleEdges( mesh.topology ) ); + + auto mesh1 = mesh; + + // independently produced plans can result in multiple edges after execution: + EXPECT_FALSE( isFillingMultipleEdgeFree( mesh.topology, p1 ) ); + executeHoleFillPlan( mesh, e.sym(), p1 ); + EXPECT_EQ( mesh.topology.numValidFaces(), 4 ); + EXPECT_TRUE( mesh.topology.isClosed() ); + EXPECT_TRUE( hasMultipleEdges( mesh.topology ) ); + + // if the plan to fill the second hole is prepared after the first hole is filled, no multiple edges appear + auto p11 = getPlanarHoleFillPlan( mesh1, e.sym() ); + EXPECT_EQ( p11.items.size(), 1 ); + EXPECT_EQ( p11.numTris, 2 ); + EXPECT_TRUE( isFillingMultipleEdgeFree( mesh1.topology, p11 ) ); + executeHoleFillPlan( mesh1, e.sym(), p11 ); + EXPECT_EQ( mesh1.topology.numValidFaces(), 4 ); + EXPECT_TRUE( mesh1.topology.isClosed() ); + EXPECT_FALSE( hasMultipleEdges( mesh1.topology ) ); +} + } //namespace MR