From 2667c640a3cb2e959d08db73ba749ac4f6237797 Mon Sep 17 00:00:00 2001 From: Fedr Date: Thu, 24 Aug 2023 21:01:03 +0300 Subject: [PATCH] WatershedGraph improvements, e.g. special "outside" basin (#1559) * WatershedGraph: special "outside" basin * parentBasin_ --- source/MRMesh/MRGraph.h | 6 ++ source/MRMesh/MRWatershedGraph.cpp | 104 +++++++++++++++++++---------- source/MRMesh/MRWatershedGraph.h | 32 +++++++-- 3 files changed, 102 insertions(+), 40 deletions(-) diff --git a/source/MRMesh/MRGraph.h b/source/MRMesh/MRGraph.h index 428c80be9240..69367afa4715 100644 --- a/source/MRMesh/MRGraph.h +++ b/source/MRMesh/MRGraph.h @@ -54,9 +54,15 @@ class Graph /// returns all valid vertices in the graph [[nodiscard]] const VertBitSet & validVerts() const { return validVerts_; } + /// returns true if given vertex is valid + [[nodiscard]] bool valid( VertId v ) const { return validVerts_.test( v ); } + /// returns all valid edges in the graph [[nodiscard]] const EdgeBitSet & validEdges() const { return validEdges_; } + /// returns true if given edge is valid + [[nodiscard]] bool valid( EdgeId e ) const { return validEdges_.test( e ); } + /// returns all edges adjacent to given vertex [[nodiscard]] const Neighbours & neighbours( VertId v ) const { return neighboursPerVertex_[v]; } diff --git a/source/MRMesh/MRWatershedGraph.cpp b/source/MRMesh/MRWatershedGraph.cpp index c5288f61d388..db4eff969254 100644 --- a/source/MRMesh/MRWatershedGraph.cpp +++ b/source/MRMesh/MRWatershedGraph.cpp @@ -3,6 +3,7 @@ #include "MRphmap.h" #include "MRRingIterator.h" #include "MRBitSetParallelFor.h" +#include "MRParallelFor.h" #include "MRTimer.h" namespace std @@ -37,9 +38,14 @@ WatershedGraph::WatershedGraph( const MeshTopology & topology, const VertScalars basins_.clear(); bds_.clear(); + outsideId_ = Graph::VertId( numBasins ); + ++numBasins; basins_.resize( numBasins ); Graph::NeighboursPerVertex neighboursPerVertex( numBasins ); - ufBasins_.reset( numBasins ); + parentBasin_.clear(); + parentBasin_.reserve( numBasins ); + for ( Graph::VertId v( 0 ); v < numBasins; ++v ) + parentBasin_.push_back( v ); Graph::EndsPerEdge endsPerEdge; HashMap neiBasins2edge; @@ -52,9 +58,7 @@ WatershedGraph::WatershedGraph( const MeshTopology & topology, const VertScalars for ( auto e : orgRing( topology, v ) ) { auto l = topology.left( e ); - if ( !l ) - continue; - Graph::VertId basin( face2basin[l] ); + Graph::VertId basin( l ? face2basin[l] : outsideId_ ); if ( !basin0 ) { basin0 = basin; @@ -79,16 +83,12 @@ WatershedGraph::WatershedGraph( const MeshTopology & topology, const VertScalars for ( auto e : orgRing( topology, v ) ) { auto l = topology.left( e ); - if ( !l ) - continue; - const Graph::VertId basinL( face2basin[l] ); + const Graph::VertId basinL( l ? face2basin[l] : outsideId_ ); auto & infoL = basins_[basinL]; if ( h < getHeightAt( infoL.lowestVert ) ) infoL.lowestVert = v; auto r = topology.right( e ); - if ( !r ) - continue; - const Graph::VertId basinR( face2basin[r] ); + const Graph::VertId basinR( r ? face2basin[r] : outsideId_ ); if ( basinL == basinR ) continue; @@ -114,6 +114,27 @@ WatershedGraph::WatershedGraph( const MeshTopology & topology, const VertScalars graph_.construct( std::move( neighboursPerVertex ), std::move( endsPerEdge ) ); } +Graph::VertId WatershedGraph::getRootBasin( Graph::VertId v ) const +{ + assert( v ); + for (;;) + { + auto p = parentBasin_[v]; + if ( p == v ) + return v; + v = p; + } +} + +void WatershedGraph::setParentsToRoots() +{ + MR_TIMER + ParallelFor( parentBasin_, [&]( Graph::VertId v ) + { + parentBasin_[v] = getRootBasin( v ); + } ); +} + MRMESH_API std::pair WatershedGraph::findLowestBd() const { MR_TIMER @@ -122,6 +143,8 @@ MRMESH_API std::pair WatershedGraph::findLowestBd() const for ( auto ei : graph_.validEdges() ) { const auto ends = graph_.ends( ei ); + if ( ends.v0 == outsideId_ || ends.v1 == outsideId_ ) + continue; const auto l0 = getHeightAt( basins_[ends.v0].lowestVert ); const auto l1 = getHeightAt( basins_[ends.v1].lowestVert ); const auto le = getHeightAt( bds_[ei].lowestVert ); @@ -136,36 +159,47 @@ MRMESH_API std::pair WatershedGraph::findLowestBd() const return { lowestEdge, lowestLevel }; } -void WatershedGraph::mergeViaBd( Graph::EdgeId bd ) +Graph::VertId WatershedGraph::merge( Graph::VertId v0, Graph::VertId v1 ) { MR_TIMER - if ( !bd ) - return; - - const auto ends = graph_.ends( bd ); - const auto [vremnant, united] = ufBasins_.unite( ends.v0, ends.v1 ); - assert( united ); - const auto vdead = ends.otherEnd( vremnant ); - { - if ( getHeightAt( basins_[vdead].lowestVert ) < getHeightAt( basins_[vremnant].lowestVert ) ) - basins_[vremnant].lowestVert = basins_[vdead].lowestVert; - } - graph_.merge( vremnant, vdead, [&]( Graph::EdgeId eremnant, Graph::EdgeId edead ) + assert( v0 && v1 ); + assert( v0 != outsideId_ && v1 != outsideId_ ); + assert( graph_.valid( v0 ) && graph_.valid( v1 ) ); + if ( v0 == v1 ) + return v0; + + assert( parentBasin_[v1] == v1 ); + parentBasin_[v1] = v0; + if ( getHeightAt( basins_[v1].lowestVert ) < getHeightAt( basins_[v0].lowestVert ) ) + basins_[v0].lowestVert = basins_[v1].lowestVert; + + graph_.merge( v0, v1, [&]( Graph::EdgeId eremnant, Graph::EdgeId edead ) { if ( getHeightAt( bds_[edead].lowestVert ) < getHeightAt( bds_[eremnant].lowestVert ) ) bds_[eremnant].lowestVert = bds_[edead].lowestVert; } ); + return v0; +} + +Graph::VertId WatershedGraph::mergeViaBd( Graph::EdgeId bd ) +{ + assert( bd ); + const auto ends = graph_.ends( bd ); + return merge( ends.v0, ends.v1 ); } FaceBitSet WatershedGraph::getBasinFaces( Graph::VertId basin ) const { MR_TIMER - FaceBitSet res( topology_.faceSize() ); - const auto & roots = ufBasins_.roots(); - assert( basin == roots[basin] ); + FaceBitSet res; + if ( basin == outsideId_ ) + return res; + res.resize( topology_.faceSize() ); + assert( graph_.valid( basin ) ); + assert( basin == parentBasin_[basin] ); BitSetParallelForAll( res, [&]( FaceId f ) { - if ( basin == roots[Graph::VertId( face2iniBasin_[f] )] ) + if ( basin == getRootBasin( Graph::VertId( face2iniBasin_[f] ) ) ) res.set( f ); } ); return res; @@ -174,12 +208,15 @@ FaceBitSet WatershedGraph::getBasinFaces( Graph::VertId basin ) const FaceBitSet WatershedGraph::getBasinFacesBelowLevel( Graph::VertId basin, float waterLevel ) const { MR_TIMER - FaceBitSet res( topology_.faceSize() ); - const auto & roots = ufBasins_.roots(); - assert( basin == roots[basin] ); + FaceBitSet res; + if ( basin == outsideId_ ) + return res; + res.resize( topology_.faceSize() ); + assert( graph_.valid( basin ) ); + assert( basin == parentBasin_[basin] ); BitSetParallelForAll( res, [&]( FaceId f ) { - if ( basin != roots[Graph::VertId( face2iniBasin_[f] )] ) + if ( basin != getRootBasin( Graph::VertId( face2iniBasin_[f] ) ) ) return; VertId vs[3]; topology_.getTriVerts( f, vs ); @@ -197,7 +234,6 @@ UndirectedEdgeBitSet WatershedGraph::getInterBasinEdges() const { MR_TIMER UndirectedEdgeBitSet res( topology_.undirectedEdgeSize() ); - const auto & roots = ufBasins_.roots(); BitSetParallelForAll( res, [&]( UndirectedEdgeId ue ) { auto l = topology_.left( ue ); @@ -206,8 +242,8 @@ UndirectedEdgeBitSet WatershedGraph::getInterBasinEdges() const auto r = topology_.right( ue ); if ( !r ) return; - const auto lBasin = roots[Graph::VertId( face2iniBasin_[l] )]; - const auto rBasin = roots[Graph::VertId( face2iniBasin_[r] )]; + const auto lBasin = getRootBasin( Graph::VertId( face2iniBasin_[l] ) ); + const auto rBasin = getRootBasin( Graph::VertId( face2iniBasin_[r] ) ); if ( lBasin != rBasin ) res.set( ue ); } ); diff --git a/source/MRMesh/MRWatershedGraph.h b/source/MRMesh/MRWatershedGraph.h index 8048b475d35d..8b0ddbb0c041 100644 --- a/source/MRMesh/MRWatershedGraph.h +++ b/source/MRMesh/MRWatershedGraph.h @@ -1,7 +1,6 @@ #pragma once #include "MRGraph.h" -#include "MRUnionFind.h" #include namespace MR @@ -11,13 +10,13 @@ namespace MR class WatershedGraph { public: - // associated with each vertex in graph + /// associated with each vertex in graph struct BasinInfo { VertId lowestVert; ///< in the whole basin }; - // associated with each edge in graph + /// associated with each edge in graph struct BdInfo { VertId lowestVert; ///< on this boundary @@ -33,18 +32,34 @@ class WatershedGraph /// returns underlying graph where each basin is a vertex [[nodiscard]] const Graph & graph() const { return graph_; } + /// returns the current number of basins (excluding special "outside" basin) + [[nodiscard]] int numBasins() const { return (int)graph_.validVerts().count() - 1; } + /// returns data associated with given basin [[nodiscard]] const BasinInfo & basinInfo( Graph::VertId v ) const { return basins_[v]; } /// returns data associated with given boundary between basins [[nodiscard]] const BdInfo & bdInfo( Graph::EdgeId e ) const { return bds_[e]; } + /// returns special "basin" representing outside areas of the mesh + [[nodiscard]] Graph::VertId outsideId() const { return outsideId_; } + + /// for valid basin return its id; for invalid basin returns the id of basin it was merged in + [[nodiscard]] MRMESH_API Graph::VertId getRootBasin( Graph::VertId v ) const; + + /// replaces parent of each basin with its computed root; + /// this speeds up following calls to getRootBasin() + MRMESH_API void setParentsToRoots(); + /// finds the lowest boundary between basins and its height, which is defined /// as the minimal different between lowest boundary point and lowest point in a basin [[nodiscard]] MRMESH_API std::pair findLowestBd() const; - /// merge two basins sharing given boundary - MRMESH_API void mergeViaBd( Graph::EdgeId bd ); + /// merges basin v1 into basin v0, v1 is deleted after that, returns v0 + MRMESH_API Graph::VertId merge( Graph::VertId v0, Graph::VertId v1 ); + + /// merges two basins sharing given boundary, returns remaining basin + MRMESH_API Graph::VertId mergeViaBd( Graph::EdgeId bd ); /// returns the mesh faces of given basin [[nodiscard]] MRMESH_API FaceBitSet getBasinFaces( Graph::VertId basin ) const; @@ -63,7 +78,12 @@ class WatershedGraph Graph graph_; Vector basins_; Vector bds_; - mutable UnionFind ufBasins_; + + /// special "basin" representing outside areas of the mesh + Graph::VertId outsideId_; + + /// for valid basin, parent is the same; for invalid basin, sequence of parents point on valid root basin + Vector parentBasin_; }; } //namespace MR