Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 56 additions & 17 deletions source/MRMesh/MRTunnelDetector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,33 @@ std::vector<EdgeLoop> findShortestEquivalentLoops( const MeshPart& mp, const Edg
return findSmallestMetricEquivalentLoops( mp.mesh.topology, loop, edgeLengthMetric( mp.mesh ), mp.region );
}

bool isLoopOuter( const Mesh& mesh, const EdgeLoop& loop )
{
assert( isEdgeLoop( mesh.topology, loop ) );
if ( loop.empty() )
return false;

Vector3f sumLenPos;
float sumLen = 0;
for ( EdgeId e : loop )
{
const auto elen = mesh.edgeLength( e );
sumLenPos += elen * mesh.edgeCenter( e );
sumLen += elen;
}
const Vector3f center = sumLenPos / sumLen;

float sumMetric = 0;
for ( EdgeId e : loop )
{
const auto elen = mesh.edgeLength( e );
const auto epos = mesh.edgeCenter( e );
const auto enorm = mesh.pseudonormal( e );
sumMetric += elen * dot( epos - center, enorm );
}
return sumMetric > 0;
}

Expected<FaceBitSet> detectTunnelFaces( const MeshPart & mp, const DetectTunnelSettings & settings )
{
MR_TIMER;
Expand All @@ -431,40 +458,46 @@ Expected<FaceBitSet> detectTunnelFaces( const MeshPart & mp, const DetectTunnelS
auto basisTunnels = d.detect( MR::subprogress( settings.progress, initialProgress, targetProgress ) );
if ( !basisTunnels.has_value() )
return unexpected( basisTunnels.error() );
auto& allLoops = *basisTunnels;
const auto numBasisTunnels = allLoops.size();

if ( settings.buildCoLoops )
{
ParallelFor( *basisTunnels, [&]( size_t i )
allLoops.resize( numBasisTunnels * 2 );
ParallelFor( size_t( 0 ), numBasisTunnels, [&]( size_t i )
{
if ( auto maybeCoLoop = findSmallestMetricCoLoop( mp.mesh.topology, (*basisTunnels)[i], metric, &activeRegion ) )
(*basisTunnels)[i] = std::move( maybeCoLoop.value() );
else
if ( auto maybeCoLoop = findSmallestMetricCoLoop( mp.mesh.topology, allLoops[i], metric, &activeRegion ) )
{
assert( false );
(*basisTunnels)[i].clear();
// first co-loop is always from a distinct topology class than original loop, so add co-loop in the list
allLoops[numBasisTunnels + i] = std::move( maybeCoLoop.value() );

// second co-loop can be from the same topology class as original loop, but with smaller metric, so replace original loop
if ( auto maybeCoLoop2 = findSmallestMetricCoLoop( mp.mesh.topology, allLoops[numBasisTunnels + i], metric, &activeRegion ) )
allLoops[i] = std::move( maybeCoLoop2.value() );
else assert( false );
}
else assert( false );
} );
std::erase_if( *basisTunnels, [](const auto& v) { return v.empty(); } );
std::erase_if( allLoops, [](const auto& v) { return v.empty(); } );
}

const auto numBasisTunnels = basisTunnels->size();

// sort loops by the sum of the given metric in ascending order
sortPathsByMetric( *basisTunnels, metric );
for ( int i = 0; i < basisTunnels->size(); ++i )
sortPathsByMetric( allLoops, metric );
for ( int i = 0; i < allLoops.size(); ++i )
{
// and skip too long loops by their euclidean length, and not by the given metric
if ( calcPathLength( (*basisTunnels)[i], mp.mesh ) > settings.maxTunnelLength )
if ( calcPathLength( (allLoops)[i], mp.mesh ) > settings.maxTunnelLength )
{
basisTunnels->erase( basisTunnels->begin() + i, basisTunnels->end() );
allLoops.erase( allLoops.begin() + i, allLoops.end() );
break;
}
}
if ( basisTunnels->empty() )
if ( allLoops.empty() )
break;

tunnelVerts.set( 0_v, tunnelVerts.size(), false );
int numSelectedTunnels = 0;
for ( const auto & t : *basisTunnels )
for ( const auto & t : allLoops )
{
bool touchAlreadySelectedTunnel = false;
for ( EdgeId e : t )
Expand All @@ -478,6 +511,11 @@ Expected<FaceBitSet> detectTunnelFaces( const MeshPart & mp, const DetectTunnelS
if ( touchAlreadySelectedTunnel )
continue;

if ( settings.loopType == TunnelLoopType::Outer && !isLoopOuter( mp.mesh, t ) )
continue;
if ( settings.loopType == TunnelLoopType::Inner && isLoopOuter( mp.mesh, t ) )
continue;

if ( settings.buildCoLoops && settings.filterEquivalentCoLoops && numSelectedTunnels > 0 )
{
auto maybeCoLoop = findSmallestMetricCoLoop( mp.mesh.topology, t, metric, &activeRegion );
Expand All @@ -494,14 +532,15 @@ Expected<FaceBitSet> detectTunnelFaces( const MeshPart & mp, const DetectTunnelS
addLeftBand( mp.mesh.topology, t, tunnelFaces );
activeRegion -= tunnelFaces; // reduce region
}
assert( numSelectedTunnels > 0 );
if ( !reportProgress( settings.progress, targetProgress + 0.01f ) )
return unexpectedOperationCanceled();

initialProgress = targetProgress + 0.01f;
targetProgress = ( ( initialProgress + 1.0f ) * 0.5f ) - 0.01f;

if ( numSelectedTunnels >= numBasisTunnels )
if ( numSelectedTunnels == 0 )
break;
if ( numSelectedTunnels >= numBasisTunnels && ( !settings.buildCoLoops || settings.filterEquivalentCoLoops ) )
break; // maximal not-intersection set of tunnels has been used
}

Expand Down
14 changes: 14 additions & 0 deletions source/MRMesh/MRTunnelDetector.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ MRMESH_API Expected<EdgeLoop> findShortestCoLoop( const MeshPart& mp, const Edge
/// same as \ref findSmallestMetricEquivalentLoops with euclidean edge length metric
[[nodiscard]] MRMESH_API std::vector<EdgeLoop> findShortestEquivalentLoops( const MeshPart& mp, const EdgeLoop& loop );

/// returns true if the loop mostly surrounds a mesh (loop's geometrical center is located inside of mesh at an average loop's point),
/// or false if the loop mostly surrounds a tunnel inside mesh (loop's geometrical center is located outside of mesh at an average loop's point)
[[nodiscard]] MRMESH_API bool isLoopOuter( const Mesh& mesh, const EdgeLoop& loop );

enum class TunnelLoopType
{
Any,
Outer, ///< handle-like
Inner ///< tunnel-like
};

struct DetectTunnelSettings
{
/// maximal euclidean length of tunnel loops to detect
Expand All @@ -62,6 +73,9 @@ struct DetectTunnelSettings
/// this option activates their filtering out, but it is very slow
bool filterEquivalentCoLoops = false;

/// selects what kind of loops to return
TunnelLoopType loopType = TunnelLoopType::Any;

/// to report algorithm progress and cancel from outside
ProgressCallback progress;
};
Expand Down
1 change: 1 addition & 0 deletions test_python/test_fixTunnels.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def test_fix_tunnels():

settings = mrmesh.DetectTunnelSettings()
settings.maxTunnelLength = 100500
settings.filterEquivalentCoLoops = True
tunnelFaces = mrmesh.detectTunnelFaces(torus, settings)

# one circle with 2-faces width
Expand Down
Loading