Skip to content

Commit 6bb91a3

Browse files
authored
duplicateNonManifoldVertices improvements (#6353)
1 parent 10d7c4d commit 6bb91a3

2 files changed

Lines changed: 94 additions & 19 deletions

File tree

source/MRMesh/MRMeshBuilder.cpp

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,8 @@ MeshTopology fromTriangles( const Triangulation & t, const BuildSettings & setti
538538
}
539539

540540
// two incident vertices can be found using this struct
541-
struct IncidentVert {
541+
struct IncidentVert
542+
{
542543
FaceId f; // to find triangle in triangleToVertices vector
543544
VertId srcVert; // central vertex, used for sorting triangles per their incident vertices
544545
// the vertices of the triangle can be upgraded, so no reason to store VertId!
@@ -547,15 +548,20 @@ struct IncidentVert {
547548
: f(f)
548549
, srcVert( srcVert )
549550
{}
551+
552+
auto asPair() const { return std::make_pair( srcVert, f ); }
553+
friend bool operator <( const IncidentVert& l, const IncidentVert& r ) { return l.asPair() < r.asPair(); }
550554
};
551555

552-
// to find the smallest connected sequences around central vertex, where a sequence does not repeat any neighbor vertex twice.
553-
struct PathOverIncidentVert {
556+
// to find connected sequences around central vertex, where a sequence does not repeat any neighbor vertex twice.
557+
class PathOverIncidentVert
558+
{
554559
Triangulation& faceToVertices;
555560
// all iterators in [vertexBegIt, vertexEndIt) must have the same central vertex
556561
std::vector<IncidentVert>::iterator vertexBegIt, vertexEndIt;
557562
size_t lastUnvisitedIndex = 0; // pivot index. [vertexBegIt, vertexBegIt + lastUnvisitedIndex) - unvisited vertices
558563

564+
public:
559565
PathOverIncidentVert( Triangulation& triangleToVertices,
560566
std::vector<IncidentVert>& incidentItemsVector, size_t beg, size_t end )
561567
: faceToVertices( triangleToVertices )
@@ -573,19 +579,25 @@ struct PathOverIncidentVert {
573579
// first unvisited vertex
574580
VertId getFirstVertex() const
575581
{
576-
for ( auto v : faceToVertices[vertexBegIt->f] )
577-
if ( v != vertexBegIt->srcVert )
578-
return v;
582+
// below selection ensures that getNextIncidentVertex( getFirstVertex(), true ) will find nextVertex in the very first triangle
583+
const auto & vs = faceToVertices[vertexBegIt->f];
584+
if ( vs[0] == vertexBegIt->srcVert )
585+
return vs[1];
586+
if ( vs[1] == vertexBegIt->srcVert )
587+
return vs[2];
588+
if ( vs[2] == vertexBegIt->srcVert )
589+
return vs[0];
579590
assert( false );
580591
return {};
581592
}
582593

583-
// find incident unvisited vertex
584-
VertId getNextIncidentVertex( VertId v, bool triOrientation )
594+
// find incident unvisited vertex, in case of several option prefer finding the vertex not equal to preVertex
595+
VertId getNextIncidentVertex( VertId v, bool triOrientation, VertId prevVertex = {} )
585596
{
586597
if ( lastUnvisitedIndex <= 0 )
587598
return VertId( -1 );
588599

600+
auto prevIt = vertexBegIt + lastUnvisitedIndex;
589601
for ( auto it = vertexBegIt; it < vertexBegIt + lastUnvisitedIndex; ++it )
590602
{
591603
VertId nextVertex;
@@ -610,11 +622,22 @@ struct PathOverIncidentVert {
610622
}
611623
if ( nextVertex )
612624
{
613-
--lastUnvisitedIndex;
614-
std::iter_swap( it, vertexBegIt + lastUnvisitedIndex );
615-
return nextVertex;
625+
if ( nextVertex != prevVertex )
626+
{
627+
--lastUnvisitedIndex;
628+
std::iter_swap( it, vertexBegIt + lastUnvisitedIndex );
629+
return nextVertex;
630+
}
631+
// prevVertex is a possible continuation, store it, and search for other options
632+
prevIt = it;
616633
}
617-
634+
}
635+
if ( prevIt < vertexBegIt + lastUnvisitedIndex )
636+
{
637+
// the only option is return in prevVertex
638+
--lastUnvisitedIndex;
639+
std::iter_swap( prevIt, vertexBegIt + lastUnvisitedIndex );
640+
return prevVertex;
618641
}
619642
return {};
620643
}
@@ -684,11 +707,7 @@ void preprocessTriangles( const Triangulation & t, FaceBitSet * region, std::vec
684707
incidentVertVector.emplace_back( f, vs[i] );
685708
}
686709

687-
tbb::parallel_sort( incidentVertVector.begin(), incidentVertVector.end(),
688-
[] ( const IncidentVert& lhv, const IncidentVert& rhv ) -> bool
689-
{
690-
return lhv.srcVert < rhv.srcVert;
691-
} );
710+
tbb::parallel_sort( incidentVertVector.begin(), incidentVertVector.end() );
692711
}
693712

694713
// path = {abcDefgD} => closedPath = {DefgD}; path = {abc}
@@ -747,6 +766,7 @@ size_t duplicateNonManifoldVertices( Triangulation & t, FaceBitSet * region, std
747766
bool triOrientation = true;
748767
const VertId firstVertex = incidentItems.getFirstVertex();
749768
visitedVertices.autoResizeSet( firstVertex );
769+
VertId prevVertex = firstVertex;
750770
VertId nextVertex = incidentItems.getNextIncidentVertex( firstVertex, triOrientation );
751771
if ( !nextVertex )
752772
{
@@ -759,7 +779,12 @@ size_t duplicateNonManifoldVertices( Triangulation & t, FaceBitSet * region, std
759779
path = { firstVertex, nextVertex };
760780
while ( true )
761781
{
762-
nextVertex = incidentItems.getNextIncidentVertex( nextVertex, triOrientation );
782+
{
783+
// prefer finding nextVertex not equal to prevVertex to maximize neighbour ring sizes
784+
auto currVertex = nextVertex;
785+
nextVertex = incidentItems.getNextIncidentVertex( currVertex, triOrientation, prevVertex );
786+
prevVertex = currVertex;
787+
}
763788

764789
if ( !nextVertex )
765790
{

source/MRTest/MRMeshBuilderTests.cpp

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <MRMesh/MRMeshBuilder.h>
22
#include <MRMesh/MRMeshBuilderTypes.h>
3-
#include <MRMesh/MRBitSet.h>
3+
#include <MRMesh/MRMeshLoad.h>
4+
#include <MRMesh/MRMeshComponents.h>
45
#include <gtest/gtest.h>
56

67
namespace MR
@@ -37,6 +38,55 @@ TEST( MRMesh, duplicateNonManifoldVertices )
3738
ASSERT_EQ( t[i][0], 7 );
3839
}
3940

41+
static void testBuildWithDups( const char * objMesh, int numVerts, int numComps )
42+
{
43+
std::istringstream s( objMesh );
44+
auto maybeMesh = MeshLoad::fromObj( s );
45+
EXPECT_TRUE( maybeMesh );
46+
47+
EXPECT_EQ( maybeMesh->topology.numValidVerts(), numVerts );
48+
EXPECT_EQ( MeshComponents::getNumComponents( *maybeMesh ), numComps );
49+
}
50+
51+
TEST( MRMesh, MeshBuildWithDups )
52+
{
53+
// this test case passed always
54+
testBuildWithDups
55+
(
56+
"v 0 0.5 0\n"
57+
"v -0.5 0.5 -0.5\n"
58+
"v 0.5 0.5 -0.5\n"
59+
"v -0.5 0.5 0.5\n"
60+
"v 0.5 0.5 0.5\n"
61+
"f 1 2 4\n"
62+
"f 1 4 2\n"
63+
"f 2 3 1\n"
64+
"f 1 5 4\n"
65+
"f 3 5 1\n"
66+
"f 2 1 3\n"
67+
"f 3 1 5\n"
68+
"f 1 4 5\n", 6, 1
69+
);
70+
71+
// this test start passing only after recent improvements (but will fail in case vertex reordering)
72+
testBuildWithDups
73+
(
74+
"v 0 0.5 0\n"
75+
"v -0.5 0.5 -0.5\n"
76+
"v 0.5 0.5 -0.5\n"
77+
"v -0.5 0.5 0.5\n"
78+
"v 0.5 0.5 0.5\n"
79+
"f 1 2 4\n"
80+
"f 2 1 3\n"
81+
"f 1 4 5\n"
82+
"f 3 1 5\n"
83+
"f 1 4 2\n"
84+
"f 2 3 1\n"
85+
"f 1 5 4\n"
86+
"f 3 5 1\n", 6, 1
87+
);
88+
}
89+
4090
} //namespace MeshBuilder
4191

4292
} //namespace MR

0 commit comments

Comments
 (0)