From 4bd1b055cc5104b103419c4b7660bb9627145394 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 15:06:17 +0300 Subject: [PATCH 01/15] v0 --- source/MRMesh/MRMesh.vcxproj | 2 ++ source/MRMesh/MRMesh.vcxproj.filters | 6 +++++ source/MRMesh/MRSegmentMesh.cpp | 38 ++++++++++++++++++++++++++++ source/MRMesh/MRSegmentMesh.h | 15 +++++++++++ 4 files changed, 61 insertions(+) create mode 100644 source/MRMesh/MRSegmentMesh.cpp create mode 100644 source/MRMesh/MRSegmentMesh.h diff --git a/source/MRMesh/MRMesh.vcxproj b/source/MRMesh/MRMesh.vcxproj index 6acc90344694..bb1724ed8aa7 100644 --- a/source/MRMesh/MRMesh.vcxproj +++ b/source/MRMesh/MRMesh.vcxproj @@ -149,6 +149,7 @@ + @@ -465,6 +466,7 @@ + diff --git a/source/MRMesh/MRMesh.vcxproj.filters b/source/MRMesh/MRMesh.vcxproj.filters index 85c11aaf2b74..831e886c74ba 100644 --- a/source/MRMesh/MRMesh.vcxproj.filters +++ b/source/MRMesh/MRMesh.vcxproj.filters @@ -1413,6 +1413,9 @@ Source Files\MeshAlgorithm + + Source Files\Algorithms + @@ -2321,6 +2324,9 @@ Source Files\MeshAlgorithm + + Source Files\Algorithms + diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp new file mode 100644 index 000000000000..8085f7557ecc --- /dev/null +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -0,0 +1,38 @@ +#include "MRSegmentMesh.h" +#include "MRMesh.h" + +namespace MR +{ + +namespace +{ + +class MeshSegmenter +{ +public: + MeshSegmenter( const Mesh& mesh, const EdgeMetric& metric ); + +private: + const Mesh& mesh_; + const EdgeMetric& metric_; +}; + +MeshSegmenter::MeshSegmenter( const Mesh& mesh, const EdgeMetric& metric ) + : mesh_( mesh ), metric_( metric ) +{ +} + +} //anonymous namespace + +Expected segmentMesh( const Mesh& mesh, const EdgeMetric& metric ) +{ + if ( !metric ) + return unexpected( "no metric given" ); + + MeshSegmenter s( mesh, metric ); + + GroupOrder res; + return res; +} + +} //namespace MR diff --git a/source/MRMesh/MRSegmentMesh.h b/source/MRMesh/MRSegmentMesh.h new file mode 100644 index 000000000000..918bb5b17842 --- /dev/null +++ b/source/MRMesh/MRSegmentMesh.h @@ -0,0 +1,15 @@ +#pragma once + +#include "MRMeshFwd.h" +#include "MRExpected.h" +#include "MRFaceFace.h" + +namespace MR +{ + +using GroupOrder = std::vector; + +MRMESH_API Expected segmentMesh( const Mesh& mesh, + const EdgeMetric& metric ); ///< sum of this metric to be minimized for the boundaries of segments + +} //namespace MR From a6ed452acb5810a1e53202fde6b4b3ad6a0a4d1c Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 15:08:44 +0300 Subject: [PATCH 02/15] v1 --- source/MRMesh/MRSegmentMesh.cpp | 12 ++++++------ source/MRMesh/MRSegmentMesh.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 8085f7557ecc..4dcfcb90b743 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -10,26 +10,26 @@ namespace class MeshSegmenter { public: - MeshSegmenter( const Mesh& mesh, const EdgeMetric& metric ); + MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ); private: - const Mesh& mesh_; + const MeshTopology& topology_; const EdgeMetric& metric_; }; -MeshSegmenter::MeshSegmenter( const Mesh& mesh, const EdgeMetric& metric ) - : mesh_( mesh ), metric_( metric ) +MeshSegmenter::MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ) + : topology_( topology ), metric_( metric ) { } } //anonymous namespace -Expected segmentMesh( const Mesh& mesh, const EdgeMetric& metric ) +Expected segmentMesh( const MeshTopology& topology, const EdgeMetric& metric ) { if ( !metric ) return unexpected( "no metric given" ); - MeshSegmenter s( mesh, metric ); + MeshSegmenter s( topology, metric ); GroupOrder res; return res; diff --git a/source/MRMesh/MRSegmentMesh.h b/source/MRMesh/MRSegmentMesh.h index 918bb5b17842..f844d6861c87 100644 --- a/source/MRMesh/MRSegmentMesh.h +++ b/source/MRMesh/MRSegmentMesh.h @@ -9,7 +9,7 @@ namespace MR using GroupOrder = std::vector; -MRMESH_API Expected segmentMesh( const Mesh& mesh, +MRMESH_API Expected segmentMesh( const MeshTopology& topology, const EdgeMetric& metric ); ///< sum of this metric to be minimized for the boundaries of segments } //namespace MR From 2ffedd94f996ec03913935770ca583bc7b9cf996 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 16:22:15 +0300 Subject: [PATCH 03/15] v2 --- source/MRMesh/MRSegmentMesh.cpp | 67 +++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 4dcfcb90b743..9325a1ee68be 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -1,5 +1,9 @@ #include "MRSegmentMesh.h" #include "MRMesh.h" +#include "MRGraph.h" +#include "MRParallelFor.h" +#include "MRRingIterator.h" +#include "MRTimer.h" namespace MR { @@ -12,20 +16,83 @@ class MeshSegmenter public: MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ); +private: + void constructGraph_(); + private: const MeshTopology& topology_; const EdgeMetric& metric_; + + Graph graph_; + Vector graphVertMetrics_; + Vector graphEdgeMetrics_; }; MeshSegmenter::MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ) : topology_( topology ), metric_( metric ) { + constructGraph_(); +} + +void MeshSegmenter::constructGraph_() +{ + MR_TIMER; + + // initially: + // Graph::VertId = Mesh::FaceId + // Graph::EdgeId = Mesh::UndirectedEdgeId + + graphEdgeMetrics_.resize( topology_.undirectedEdgeSize() ); + Graph::EndsPerEdge ends( topology_.undirectedEdgeSize() ); + ParallelFor( graphEdgeMetrics_, [&]( Graph::EdgeId ge ) + { + UndirectedEdgeId ue( (int)ge ); + if ( topology_.isLoneEdge( ue ) ) + return; + graphEdgeMetrics_[ge] = metric_( ue ); + + Graph::EndVertices vv; + vv.v0 = Graph::VertId( (int)topology_.left( ue ) ); + vv.v1 = Graph::VertId( (int)topology_.right( ue ) ); + if ( vv.v0 && vv.v1 ) + { + assert( vv.v0 != vv.v1 ); + if ( vv.v1 < vv.v0 ) + std::swap( vv.v0, vv.v1 ); + ends[ge] = vv; + } + } ); + + graphVertMetrics_.resize( topology_.faceSize() ); + Graph::NeighboursPerVertex neis( topology_.faceSize() ); + ParallelFor( graphVertMetrics_, [&]( Graph::VertId gv ) + { + FaceId f( (int)gv ); + if ( !topology_.hasFace( f ) ) + return; + + Graph::Neighbours n; + n.reserve( topology_.getFaceDegree( f ) ); + double m = 0; + for ( auto e : leftRing( topology_, f ) ) + { + Graph::EdgeId ge( int( e.undirected() ) ); + assert( topology_.left( e ) = f ); + if ( topology_.right( e ) ) + n.push_back( ge ); + m += graphEdgeMetrics_[ge]; + } + graphVertMetrics_[gv] = m; + neis[gv] = std::move( n ); + } ); + graph_.construct( std::move( neis ), std::move( ends ) ); } } //anonymous namespace Expected segmentMesh( const MeshTopology& topology, const EdgeMetric& metric ) { + MR_TIMER; if ( !metric ) return unexpected( "no metric given" ); From 129721966db433ee89115dce322fbfa0c42a622b Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 16:32:57 +0300 Subject: [PATCH 04/15] v3 --- source/MRMesh/MRHeap.h | 12 ++++++++++++ source/MRMesh/MRSegmentMesh.cpp | 3 +++ 2 files changed, 15 insertions(+) diff --git a/source/MRMesh/MRHeap.h b/source/MRMesh/MRHeap.h index 7daf5578d6d4..e72a23988ae0 100644 --- a/source/MRMesh/MRHeap.h +++ b/source/MRMesh/MRHeap.h @@ -28,31 +28,43 @@ class Heap T val; }; + /// constructs an empty heap + explicit Heap( P pred = {} ) : pred_( pred ) {} + /// constructs heap for given number of elements, assigning given default value to each element explicit Heap( size_t size, T def MR_LIFETIMEBOUND_NESTED = {}, P pred = {} ); + /// constructs heap from given elements (id's shall not repeat and have spaces, but can be arbitrary shuffled) explicit Heap( std::vector elms MR_LIFETIMEBOUND_NESTED, P pred = {} ); + /// returns the size of the heap size_t size() const { return heap_.size(); } + /// increases the size of the heap by adding elements at the end void resize( size_t size, T def MR_LIFETIME_CAPTURE_BY_NESTED(this) = {} ); + /// returns the value associated with given element const T & value( I elemId ) const MR_LIFETIMEBOUND { return heap_[ id2PosInHeap_[ elemId ] ].val; } + /// returns the element with the largest value const Element & top() const MR_LIFETIMEBOUND { return heap_[0]; } + /// sets new value to given element void setValue( I elemId, const T & newVal MR_LIFETIME_CAPTURE_BY_NESTED(this) ); + /// sets new value to given element, which shall be larger/smaller than the current value void setLargerValue( I elemId, const T & newVal MR_LIFETIME_CAPTURE_BY_NESTED(this) ); void setSmallerValue( I elemId, const T & newVal MR_LIFETIME_CAPTURE_BY_NESTED(this) ); template void increaseValue( I elemId, const U & inc ) { setLargerValue( elemId, value( elemId ) + inc ); } + /// sets new value to the current top element, returning its previous value Element setTopValue( const T & newVal MR_LIFETIME_CAPTURE_BY_NESTED(this) ) { Element res = top(); setValue( res.id, newVal ); return res; } private: /// tests whether heap element at posA is less than posB bool less_( size_t posA, size_t posB ) const; + /// lifts the element in the queue according to its value void lift_( size_t pos, I elemId ); diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 9325a1ee68be..6223947caff9 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -3,6 +3,7 @@ #include "MRGraph.h" #include "MRParallelFor.h" #include "MRRingIterator.h" +#include "MRHeap.h" #include "MRTimer.h" namespace MR @@ -26,6 +27,8 @@ class MeshSegmenter Graph graph_; Vector graphVertMetrics_; Vector graphEdgeMetrics_; + + Heap> heap_; }; MeshSegmenter::MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ) From 83f3018f8158a4a94d09bc4016807a543a9cdbdc Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 16:51:52 +0300 Subject: [PATCH 05/15] v4 --- source/MRMesh/MRSegmentMesh.cpp | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 6223947caff9..8bcf30b00409 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -5,6 +5,7 @@ #include "MRRingIterator.h" #include "MRHeap.h" #include "MRTimer.h" +#include namespace MR { @@ -12,13 +13,18 @@ namespace MR namespace { +static constexpr double NoEdgeMetric = DBL_MAX; + class MeshSegmenter { public: MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ); private: + double vertMetricAfterMerge_( Graph::EdgeId ge ) const; + void constructGraph_(); + void constructHeap_(); private: const MeshTopology& topology_; @@ -28,13 +34,21 @@ class MeshSegmenter Vector graphVertMetrics_; Vector graphEdgeMetrics_; - Heap> heap_; + using Heap = MR::Heap>; + Heap heap_; }; MeshSegmenter::MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ) : topology_( topology ), metric_( metric ) { constructGraph_(); + constructHeap_(); +} + +inline double MeshSegmenter::vertMetricAfterMerge_( Graph::EdgeId ge ) const +{ + const auto & vv = graph_.ends( ge ); + return graphVertMetrics_[vv.v0] + graphVertMetrics_[vv.v1] - 2 * graphEdgeMetrics_[ge]; } void MeshSegmenter::constructGraph_() @@ -91,6 +105,21 @@ void MeshSegmenter::constructGraph_() graph_.construct( std::move( neis ), std::move( ends ) ); } +void MeshSegmenter::constructHeap_() +{ + MR_TIMER; + + std::vector elements( topology_.undirectedEdgeSize(), { .val = NoEdgeMetric } ); + ParallelFor( graphEdgeMetrics_, [&]( Graph::EdgeId ge ) + { + elements[ge].id = ge; + if ( !graph_.validEdges().test( ge ) ) + return; + elements[ge].val = vertMetricAfterMerge_( ge ); + } ); + heap_ = Heap( std::move( elements ) ); +} + } //anonymous namespace Expected segmentMesh( const MeshTopology& topology, const EdgeMetric& metric ) From aa023124c8a2ad10e1282aa337bbf97428269409 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 17:13:19 +0300 Subject: [PATCH 06/15] no explicit --- source/MRMesh/MRHeap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/MRMesh/MRHeap.h b/source/MRMesh/MRHeap.h index e72a23988ae0..3ff81d9a063e 100644 --- a/source/MRMesh/MRHeap.h +++ b/source/MRMesh/MRHeap.h @@ -29,7 +29,7 @@ class Heap }; /// constructs an empty heap - explicit Heap( P pred = {} ) : pred_( pred ) {} + Heap( P pred = {} ) : pred_( pred ) {} /// constructs heap for given number of elements, assigning given default value to each element explicit Heap( size_t size, T def MR_LIFETIMEBOUND_NESTED = {}, P pred = {} ); From 45a02d4d7b229e63b0ced3b9249868ea6df40a79 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 19:05:00 +0300 Subject: [PATCH 07/15] v5 --- source/MRMesh/MRSegmentMesh.cpp | 39 +++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 8bcf30b00409..78ad2dcb0e12 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -6,6 +6,7 @@ #include "MRHeap.h" #include "MRTimer.h" #include +#include namespace MR { @@ -19,12 +20,14 @@ class MeshSegmenter { public: MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ); + GroupOrder run(); private: double vertMetricAfterMerge_( Graph::EdgeId ge ) const; void constructGraph_(); void constructHeap_(); + std::optional mergeNext_(); private: const MeshTopology& topology_; @@ -113,13 +116,43 @@ void MeshSegmenter::constructHeap_() ParallelFor( graphEdgeMetrics_, [&]( Graph::EdgeId ge ) { elements[ge].id = ge; - if ( !graph_.validEdges().test( ge ) ) + if ( !graph_.valid( ge ) ) return; elements[ge].val = vertMetricAfterMerge_( ge ); } ); heap_ = Heap( std::move( elements ) ); } +std::optional MeshSegmenter::mergeNext_() +{ + const auto [ge, metric] = heap_.top(); + if ( metric == NoEdgeMetric ) + return std::nullopt; + assert( graph_.valid( ge ) ); + assert( metric == vertMetricAfterMerge_( ge ) ); + heap_.setSmallerValue( ge, NoEdgeMetric ); + + auto ends = graph_.ends( ge ); + graphVertMetrics_[ends.v0] = metric; + graph_.merge( ends.v0, ends.v1, [&]( Graph::EdgeId remnant, Graph::EdgeId dead ) + { + graphEdgeMetrics_[remnant] += graphEdgeMetrics_[dead]; + heap_.setValue( remnant, vertMetricAfterMerge_( remnant ) ); + heap_.setSmallerValue( dead, NoEdgeMetric ); + } ); + + return ends; +} + +GroupOrder MeshSegmenter::run() +{ + MR_TIMER; + GroupOrder res; + while ( auto vv = mergeNext_() ) + res.push_back( { FaceId( int( vv->v0 ) ), FaceId( int( vv->v1 ) ) } ); + return res; +} + } //anonymous namespace Expected segmentMesh( const MeshTopology& topology, const EdgeMetric& metric ) @@ -129,9 +162,7 @@ Expected segmentMesh( const MeshTopology& topology, const EdgeMetric return unexpected( "no metric given" ); MeshSegmenter s( topology, metric ); - - GroupOrder res; - return res; + return s.run(); } } //namespace MR From 628313095e8c55ebd37165d5153f15863c65be9b Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 19:35:01 +0300 Subject: [PATCH 08/15] v6 --- source/MRMesh/MRSegmentMesh.cpp | 38 ++++++++++++++++++++++++++++++++- source/MRMesh/MRSegmentMesh.h | 3 +++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 78ad2dcb0e12..1204cac01d30 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -2,8 +2,10 @@ #include "MRMesh.h" #include "MRGraph.h" #include "MRParallelFor.h" +#include "MRBitSetParallelFor.h" #include "MRRingIterator.h" #include "MRHeap.h" +#include "MRUnionFind.h" #include "MRTimer.h" #include #include @@ -103,6 +105,7 @@ void MeshSegmenter::constructGraph_() m += graphEdgeMetrics_[ge]; } graphVertMetrics_[gv] = m; + std::sort( n.begin(), n.end() ); neis[gv] = std::move( n ); } ); graph_.construct( std::move( neis ), std::move( ends ) ); @@ -129,7 +132,8 @@ std::optional MeshSegmenter::mergeNext_() if ( metric == NoEdgeMetric ) return std::nullopt; assert( graph_.valid( ge ) ); - assert( metric == vertMetricAfterMerge_( ge ) ); + [[maybe_unused]] auto m0 = vertMetricAfterMerge_( ge ); + assert( metric == m0 ); heap_.setSmallerValue( ge, NoEdgeMetric ); auto ends = graph_.ends( ge ); @@ -165,4 +169,36 @@ Expected segmentMesh( const MeshTopology& topology, const EdgeMetric return s.run(); } +UndirectedEdgeBitSet findSegmentBoundaries( const MeshTopology& topology, + const GroupOrder& groupOrder, int numSegments ) +{ + MR_TIMER; + int numMerges = topology.numValidFaces() - numSegments; + assert( numMerges >= 0 ); + assert( numMerges <= groupOrder.size() ); + numMerges = std::clamp( numMerges, 0, (int)groupOrder.size() ); + + UnionFind unionFindStruct( topology.faceSize() ); + for ( int i = 0; i < numMerges; ++i ) + { + [[maybe_unused]] auto p = unionFindStruct.unite( groupOrder[i].aFace, groupOrder[i].bFace ); + assert( p.second ); + } + const auto & roots = unionFindStruct.roots(); + + UndirectedEdgeBitSet res( topology.undirectedEdgeSize() ); + BitSetParallelForAll( res, [&]( UndirectedEdgeId ue ) + { + const auto l = topology.left( ue ); + if ( !l ) + return; + const auto r = topology.right( ue ); + if ( !r ) + return; + if ( roots[l] != roots[r] ) + res.set( ue ); + } ); + return res; +} + } //namespace MR diff --git a/source/MRMesh/MRSegmentMesh.h b/source/MRMesh/MRSegmentMesh.h index f844d6861c87..6cf3b0c66acb 100644 --- a/source/MRMesh/MRSegmentMesh.h +++ b/source/MRMesh/MRSegmentMesh.h @@ -12,4 +12,7 @@ using GroupOrder = std::vector; MRMESH_API Expected segmentMesh( const MeshTopology& topology, const EdgeMetric& metric ); ///< sum of this metric to be minimized for the boundaries of segments +[[nodiscard]] MRMESH_API UndirectedEdgeBitSet findSegmentBoundaries( const MeshTopology& topology, + const GroupOrder& groupOrder, int numSegments ); + } //namespace MR From fa2f7af28c872d0528b2ed4ffee4a4b59667f7f0 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 20:12:17 +0300 Subject: [PATCH 09/15] v7 --- source/MRMesh/MRSegmentMesh.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 1204cac01d30..8a2a34db5770 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -132,8 +132,7 @@ std::optional MeshSegmenter::mergeNext_() if ( metric == NoEdgeMetric ) return std::nullopt; assert( graph_.valid( ge ) ); - [[maybe_unused]] auto m0 = vertMetricAfterMerge_( ge ); - assert( metric == m0 ); + assert( metric == vertMetricAfterMerge_( ge ) ); heap_.setSmallerValue( ge, NoEdgeMetric ); auto ends = graph_.ends( ge ); @@ -141,10 +140,12 @@ std::optional MeshSegmenter::mergeNext_() graph_.merge( ends.v0, ends.v1, [&]( Graph::EdgeId remnant, Graph::EdgeId dead ) { graphEdgeMetrics_[remnant] += graphEdgeMetrics_[dead]; - heap_.setValue( remnant, vertMetricAfterMerge_( remnant ) ); heap_.setSmallerValue( dead, NoEdgeMetric ); } ); + for ( auto ne : graph_.neighbours( ends.v0 ) ) + heap_.setValue( ne, vertMetricAfterMerge_( ne ) ); + return ends; } From a24dc69690e36439c377235d085f4326b4fdf19f Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Tue, 7 Apr 2026 13:03:07 +0300 Subject: [PATCH 10/15] v8 --- source/MRMesh/MRSegmentMesh.cpp | 54 ++++++++++++++++----------------- source/MRMesh/MRSegmentMesh.h | 4 +-- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 8a2a34db5770..ed3bc0ef005e 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -21,7 +21,7 @@ static constexpr double NoEdgeMetric = DBL_MAX; class MeshSegmenter { public: - MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ); + MeshSegmenter( const Mesh& mesh, const EdgeMetric& lengthCurvMetric ); GroupOrder run(); private: @@ -32,8 +32,8 @@ class MeshSegmenter std::optional mergeNext_(); private: - const MeshTopology& topology_; - const EdgeMetric& metric_; + const Mesh& mesh_; + const EdgeMetric& lengthCurvMetric_; Graph graph_; Vector graphVertMetrics_; @@ -43,8 +43,8 @@ class MeshSegmenter Heap heap_; }; -MeshSegmenter::MeshSegmenter( const MeshTopology& topology, const EdgeMetric& metric ) - : topology_( topology ), metric_( metric ) +MeshSegmenter::MeshSegmenter( const Mesh& mesh, const EdgeMetric& lengthCurvMetric ) + : mesh_( mesh ), lengthCurvMetric_( lengthCurvMetric ) { constructGraph_(); constructHeap_(); @@ -64,18 +64,18 @@ void MeshSegmenter::constructGraph_() // Graph::VertId = Mesh::FaceId // Graph::EdgeId = Mesh::UndirectedEdgeId - graphEdgeMetrics_.resize( topology_.undirectedEdgeSize() ); - Graph::EndsPerEdge ends( topology_.undirectedEdgeSize() ); + graphEdgeMetrics_.resize( mesh_.topology.undirectedEdgeSize() ); + Graph::EndsPerEdge ends( mesh_.topology.undirectedEdgeSize() ); ParallelFor( graphEdgeMetrics_, [&]( Graph::EdgeId ge ) { UndirectedEdgeId ue( (int)ge ); - if ( topology_.isLoneEdge( ue ) ) + if ( mesh_.topology.isLoneEdge( ue ) ) return; - graphEdgeMetrics_[ge] = metric_( ue ); + graphEdgeMetrics_[ge] = lengthCurvMetric_( ue ); Graph::EndVertices vv; - vv.v0 = Graph::VertId( (int)topology_.left( ue ) ); - vv.v1 = Graph::VertId( (int)topology_.right( ue ) ); + vv.v0 = Graph::VertId( (int)mesh_.topology.left( ue ) ); + vv.v1 = Graph::VertId( (int)mesh_.topology.right( ue ) ); if ( vv.v0 && vv.v1 ) { assert( vv.v0 != vv.v1 ); @@ -85,22 +85,22 @@ void MeshSegmenter::constructGraph_() } } ); - graphVertMetrics_.resize( topology_.faceSize() ); - Graph::NeighboursPerVertex neis( topology_.faceSize() ); + graphVertMetrics_.resize( mesh_.topology.faceSize() ); + Graph::NeighboursPerVertex neis( mesh_.topology.faceSize() ); ParallelFor( graphVertMetrics_, [&]( Graph::VertId gv ) { FaceId f( (int)gv ); - if ( !topology_.hasFace( f ) ) + if ( !mesh_.topology.hasFace( f ) ) return; Graph::Neighbours n; - n.reserve( topology_.getFaceDegree( f ) ); + n.reserve( mesh_.topology.getFaceDegree( f ) ); double m = 0; - for ( auto e : leftRing( topology_, f ) ) + for ( auto e : leftRing( mesh_.topology, f ) ) { Graph::EdgeId ge( int( e.undirected() ) ); - assert( topology_.left( e ) = f ); - if ( topology_.right( e ) ) + assert( mesh_.topology.left( e ) = f ); + if ( mesh_.topology.right( e ) ) n.push_back( ge ); m += graphEdgeMetrics_[ge]; } @@ -115,7 +115,7 @@ void MeshSegmenter::constructHeap_() { MR_TIMER; - std::vector elements( topology_.undirectedEdgeSize(), { .val = NoEdgeMetric } ); + std::vector elements( mesh_.topology.undirectedEdgeSize(), { .val = NoEdgeMetric } ); ParallelFor( graphEdgeMetrics_, [&]( Graph::EdgeId ge ) { elements[ge].id = ge; @@ -128,15 +128,15 @@ void MeshSegmenter::constructHeap_() std::optional MeshSegmenter::mergeNext_() { - const auto [ge, metric] = heap_.top(); - if ( metric == NoEdgeMetric ) + const auto [ge, lengthCurvMetric] = heap_.top(); + if ( lengthCurvMetric == NoEdgeMetric ) return std::nullopt; assert( graph_.valid( ge ) ); - assert( metric == vertMetricAfterMerge_( ge ) ); + assert( lengthCurvMetric == vertMetricAfterMerge_( ge ) ); heap_.setSmallerValue( ge, NoEdgeMetric ); auto ends = graph_.ends( ge ); - graphVertMetrics_[ends.v0] = metric; + graphVertMetrics_[ends.v0] = lengthCurvMetric; graph_.merge( ends.v0, ends.v1, [&]( Graph::EdgeId remnant, Graph::EdgeId dead ) { graphEdgeMetrics_[remnant] += graphEdgeMetrics_[dead]; @@ -160,13 +160,13 @@ GroupOrder MeshSegmenter::run() } //anonymous namespace -Expected segmentMesh( const MeshTopology& topology, const EdgeMetric& metric ) +Expected segmentMesh( const Mesh& mesh, const EdgeMetric& lengthCurvMetric ) { MR_TIMER; - if ( !metric ) - return unexpected( "no metric given" ); + if ( !lengthCurvMetric ) + return unexpected( "no lengthCurvMetric given" ); - MeshSegmenter s( topology, metric ); + MeshSegmenter s( mesh, lengthCurvMetric ); return s.run(); } diff --git a/source/MRMesh/MRSegmentMesh.h b/source/MRMesh/MRSegmentMesh.h index 6cf3b0c66acb..9bbeb7b1b14c 100644 --- a/source/MRMesh/MRSegmentMesh.h +++ b/source/MRMesh/MRSegmentMesh.h @@ -9,8 +9,8 @@ namespace MR using GroupOrder = std::vector; -MRMESH_API Expected segmentMesh( const MeshTopology& topology, - const EdgeMetric& metric ); ///< sum of this metric to be minimized for the boundaries of segments +MRMESH_API Expected segmentMesh( const Mesh& mesh, + const EdgeMetric& lengthCurvMetric ); ///< sum of this metric to be maximize for the boundaries of segments [[nodiscard]] MRMESH_API UndirectedEdgeBitSet findSegmentBoundaries( const MeshTopology& topology, const GroupOrder& groupOrder, int numSegments ); From 314d3e03f9435d1d06662cb8c9c285eb3a4e7827 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Tue, 7 Apr 2026 13:30:43 +0300 Subject: [PATCH 11/15] v9 --- source/MRMesh/MRSegmentMesh.cpp | 68 +++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index ed3bc0ef005e..843611d52991 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -16,7 +16,7 @@ namespace MR namespace { -static constexpr double NoEdgeMetric = DBL_MAX; +static constexpr double ProhibitMergePenalty = DBL_MAX; class MeshSegmenter { @@ -25,7 +25,7 @@ class MeshSegmenter GroupOrder run(); private: - double vertMetricAfterMerge_( Graph::EdgeId ge ) const; + double mergePenalty_( Graph::EdgeId ge ) const; void constructGraph_(); void constructHeap_(); @@ -36,8 +36,21 @@ class MeshSegmenter const EdgeMetric& lengthCurvMetric_; Graph graph_; - Vector graphVertMetrics_; - Vector graphEdgeMetrics_; + + struct SegmentData + { + double area = 0; + SegmentData& operator +=( const SegmentData& r ) { area += r.area; return * this; } + }; + Vector graphVertData_; + + struct SegmentBdData + { + double length = 0; + double lengthCurv = 0; + SegmentBdData& operator +=( const SegmentBdData& r ) { length += r.length; lengthCurv += r.lengthCurv; return * this; } + }; + Vector graphEdgeData_; using Heap = MR::Heap>; Heap heap_; @@ -50,10 +63,13 @@ MeshSegmenter::MeshSegmenter( const Mesh& mesh, const EdgeMetric& lengthCurvMetr constructHeap_(); } -inline double MeshSegmenter::vertMetricAfterMerge_( Graph::EdgeId ge ) const +inline double MeshSegmenter::mergePenalty_( Graph::EdgeId ge ) const { + const auto & ed = graphEdgeData_[ge]; + if ( ed.length <= 0 ) + return ProhibitMergePenalty; const auto & vv = graph_.ends( ge ); - return graphVertMetrics_[vv.v0] + graphVertMetrics_[vv.v1] - 2 * graphEdgeMetrics_[ge]; + return std::min( graphVertData_[vv.v0].area, graphVertData_[vv.v1].area ) * ed.lengthCurv / sqr( ed.length ); } void MeshSegmenter::constructGraph_() @@ -64,14 +80,18 @@ void MeshSegmenter::constructGraph_() // Graph::VertId = Mesh::FaceId // Graph::EdgeId = Mesh::UndirectedEdgeId - graphEdgeMetrics_.resize( mesh_.topology.undirectedEdgeSize() ); + graphEdgeData_.resize( mesh_.topology.undirectedEdgeSize() ); Graph::EndsPerEdge ends( mesh_.topology.undirectedEdgeSize() ); - ParallelFor( graphEdgeMetrics_, [&]( Graph::EdgeId ge ) + ParallelFor( graphEdgeData_, [&]( Graph::EdgeId ge ) { UndirectedEdgeId ue( (int)ge ); if ( mesh_.topology.isLoneEdge( ue ) ) return; - graphEdgeMetrics_[ge] = lengthCurvMetric_( ue ); + graphEdgeData_[ge] = + { + .length = mesh_.edgeLength( ue ), + .lengthCurv = lengthCurvMetric_( ue ) + }; Graph::EndVertices vv; vv.v0 = Graph::VertId( (int)mesh_.topology.left( ue ) ); @@ -85,9 +105,9 @@ void MeshSegmenter::constructGraph_() } } ); - graphVertMetrics_.resize( mesh_.topology.faceSize() ); + graphVertData_.resize( mesh_.topology.faceSize() ); Graph::NeighboursPerVertex neis( mesh_.topology.faceSize() ); - ParallelFor( graphVertMetrics_, [&]( Graph::VertId gv ) + ParallelFor( graphVertData_, [&]( Graph::VertId gv ) { FaceId f( (int)gv ); if ( !mesh_.topology.hasFace( f ) ) @@ -95,16 +115,14 @@ void MeshSegmenter::constructGraph_() Graph::Neighbours n; n.reserve( mesh_.topology.getFaceDegree( f ) ); - double m = 0; for ( auto e : leftRing( mesh_.topology, f ) ) { Graph::EdgeId ge( int( e.undirected() ) ); assert( mesh_.topology.left( e ) = f ); if ( mesh_.topology.right( e ) ) n.push_back( ge ); - m += graphEdgeMetrics_[ge]; } - graphVertMetrics_[gv] = m; + graphVertData_[gv].area = mesh_.area( f ); std::sort( n.begin(), n.end() ); neis[gv] = std::move( n ); } ); @@ -115,36 +133,36 @@ void MeshSegmenter::constructHeap_() { MR_TIMER; - std::vector elements( mesh_.topology.undirectedEdgeSize(), { .val = NoEdgeMetric } ); - ParallelFor( graphEdgeMetrics_, [&]( Graph::EdgeId ge ) + std::vector elements( mesh_.topology.undirectedEdgeSize(), { .val = ProhibitMergePenalty } ); + ParallelFor( graphEdgeData_, [&]( Graph::EdgeId ge ) { elements[ge].id = ge; if ( !graph_.valid( ge ) ) return; - elements[ge].val = vertMetricAfterMerge_( ge ); + elements[ge].val = mergePenalty_( ge ); } ); heap_ = Heap( std::move( elements ) ); } std::optional MeshSegmenter::mergeNext_() { - const auto [ge, lengthCurvMetric] = heap_.top(); - if ( lengthCurvMetric == NoEdgeMetric ) + const auto [ge, penalty] = heap_.top(); + if ( penalty == ProhibitMergePenalty ) return std::nullopt; assert( graph_.valid( ge ) ); - assert( lengthCurvMetric == vertMetricAfterMerge_( ge ) ); - heap_.setSmallerValue( ge, NoEdgeMetric ); + assert( penalty == mergePenalty_( ge ) ); + heap_.setSmallerValue( ge, ProhibitMergePenalty ); auto ends = graph_.ends( ge ); - graphVertMetrics_[ends.v0] = lengthCurvMetric; + graphVertData_[ends.v0] += graphVertData_[ends.v1]; graph_.merge( ends.v0, ends.v1, [&]( Graph::EdgeId remnant, Graph::EdgeId dead ) { - graphEdgeMetrics_[remnant] += graphEdgeMetrics_[dead]; - heap_.setSmallerValue( dead, NoEdgeMetric ); + graphEdgeData_[remnant] += graphEdgeData_[dead]; + heap_.setSmallerValue( dead, ProhibitMergePenalty ); } ); for ( auto ne : graph_.neighbours( ends.v0 ) ) - heap_.setValue( ne, vertMetricAfterMerge_( ne ) ); + heap_.setValue( ne, mergePenalty_( ne ) ); return ends; } From bb55301e686355a765b74ceaef575296928a0200 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Tue, 7 Apr 2026 15:04:50 +0300 Subject: [PATCH 12/15] va --- source/MRMesh/MREdgeMetric.cpp | 17 +++++++++++++++++ source/MRMesh/MREdgeMetric.h | 13 +++++++++++++ source/MRMesh/MRSegmentMesh.cpp | 21 +++++++++++---------- source/MRMesh/MRSegmentMesh.h | 2 +- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/source/MRMesh/MREdgeMetric.cpp b/source/MRMesh/MREdgeMetric.cpp index 94bfd8a576be..c69b091f47d2 100644 --- a/source/MRMesh/MREdgeMetric.cpp +++ b/source/MRMesh/MREdgeMetric.cpp @@ -90,6 +90,23 @@ EdgeMetric edgeAbsCurvMetric( const Mesh & mesh, float angleSinFactor, float ang return edgeAbsCurvMetric( mesh.topology, mesh.points, angleSinFactor, angleSinForBoundary ); } +EdgeMetric edgeDihedralAngleMetric( const MeshTopology& topology, const VertCoords& points, const DihedralAngleProcessParams& params ) +{ + return [&topology, &points, params]( EdgeId e ) -> float + { + if ( topology.isBdEdge( e, nullptr ) ) + return params.boundaryValue; + + auto a = dihedralAngleSin( topology, points, e ); + return a >= 0 ? params.convexFactor * a : params.concaveFactor * a; + }; +} + +EdgeMetric edgeDihedralAngleMetric( const Mesh& mesh, const DihedralAngleProcessParams& params ) +{ + return edgeDihedralAngleMetric( mesh.topology, mesh.points, params ); +} + EdgeMetric edgeTableSymMetric( const MeshTopology & topology, const EdgeMetric & metric ) { MR_TIMER; diff --git a/source/MRMesh/MREdgeMetric.h b/source/MRMesh/MREdgeMetric.h index 0260a3258840..f730c9238947 100644 --- a/source/MRMesh/MREdgeMetric.h +++ b/source/MRMesh/MREdgeMetric.h @@ -44,6 +44,19 @@ namespace MR [[nodiscard]] MRMESH_API EdgeMetric edgeAbsCurvMetric( const Mesh & mesh, float angleSinFactor = 2, float angleSinForBoundary = 0 ); [[nodiscard]] MRMESH_API EdgeMetric edgeAbsCurvMetric( const MeshTopology& topology, const VertCoords& points, float angleSinFactor = 2, float angleSinForBoundary = 0 ); +struct DihedralAngleProcessParams +{ + float convexFactor = 1; ///< positive convex dihedral angles are returned multiplied on this factor + float concaveFactor = 1; ///< positive convex dihedral angles are returned multiplied on this factor + float boundaryValue = 0; ///< this value will be returned as dihedral angle for boundary edges +}; + +/// this metric returns edge's dihedral angle computed by MR::dihedralAngle and post-processed based on the given parameters; +/// returned value is NOT multiplied on edge's length as in other metrics; +/// this metric is symmetric: m(e) == m(e.sym()) +[[nodiscard]] MRMESH_API EdgeMetric edgeDihedralAngleMetric( const Mesh& mesh, const DihedralAngleProcessParams& params = {} ); +[[nodiscard]] MRMESH_API EdgeMetric edgeDihedralAngleMetric( const MeshTopology& topology, const VertCoords& points, const DihedralAngleProcessParams& params = {} ); + /// pre-computes the metric for all mesh edges to quickly return it later for any edge; /// input metric must be symmetric: metric(e) == metric(e.sym()) [[nodiscard]] MRMESH_API EdgeMetric edgeTableSymMetric( const MeshTopology & topology, const EdgeMetric & metric ); diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 843611d52991..4044bded4c13 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -21,7 +21,7 @@ static constexpr double ProhibitMergePenalty = DBL_MAX; class MeshSegmenter { public: - MeshSegmenter( const Mesh& mesh, const EdgeMetric& lengthCurvMetric ); + MeshSegmenter( const Mesh& mesh, const EdgeMetric& curvMetric ); GroupOrder run(); private: @@ -33,7 +33,7 @@ class MeshSegmenter private: const Mesh& mesh_; - const EdgeMetric& lengthCurvMetric_; + const EdgeMetric& curvMetric_; Graph graph_; @@ -56,8 +56,8 @@ class MeshSegmenter Heap heap_; }; -MeshSegmenter::MeshSegmenter( const Mesh& mesh, const EdgeMetric& lengthCurvMetric ) - : mesh_( mesh ), lengthCurvMetric_( lengthCurvMetric ) +MeshSegmenter::MeshSegmenter( const Mesh& mesh, const EdgeMetric& curvMetric ) + : mesh_( mesh ), curvMetric_( curvMetric ) { constructGraph_(); constructHeap_(); @@ -87,10 +87,11 @@ void MeshSegmenter::constructGraph_() UndirectedEdgeId ue( (int)ge ); if ( mesh_.topology.isLoneEdge( ue ) ) return; + const auto len = mesh_.edgeLength( ue ); graphEdgeData_[ge] = { - .length = mesh_.edgeLength( ue ), - .lengthCurv = lengthCurvMetric_( ue ) + .length = len, + .lengthCurv = len * curvMetric_( ue ) }; Graph::EndVertices vv; @@ -178,13 +179,13 @@ GroupOrder MeshSegmenter::run() } //anonymous namespace -Expected segmentMesh( const Mesh& mesh, const EdgeMetric& lengthCurvMetric ) +Expected segmentMesh( const Mesh& mesh, const EdgeMetric& curvMetric ) { MR_TIMER; - if ( !lengthCurvMetric ) - return unexpected( "no lengthCurvMetric given" ); + if ( !curvMetric ) + return unexpected( "no curvMetric given" ); - MeshSegmenter s( mesh, lengthCurvMetric ); + MeshSegmenter s( mesh, curvMetric ); return s.run(); } diff --git a/source/MRMesh/MRSegmentMesh.h b/source/MRMesh/MRSegmentMesh.h index 9bbeb7b1b14c..67ec265f9a73 100644 --- a/source/MRMesh/MRSegmentMesh.h +++ b/source/MRMesh/MRSegmentMesh.h @@ -10,7 +10,7 @@ namespace MR using GroupOrder = std::vector; MRMESH_API Expected segmentMesh( const Mesh& mesh, - const EdgeMetric& lengthCurvMetric ); ///< sum of this metric to be maximize for the boundaries of segments + const EdgeMetric& curvMetric ); ///< integral of this metric over segments' boundaries will be maximized [[nodiscard]] MRMESH_API UndirectedEdgeBitSet findSegmentBoundaries( const MeshTopology& topology, const GroupOrder& groupOrder, int numSegments ); From a467c7b02f84a1f31be7f3af107bbc7fbd4c9be7 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Tue, 7 Apr 2026 15:11:20 +0300 Subject: [PATCH 13/15] dihedralAngle without sin --- source/MRMesh/MREdgeMetric.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/MRMesh/MREdgeMetric.cpp b/source/MRMesh/MREdgeMetric.cpp index c69b091f47d2..5727fbf26419 100644 --- a/source/MRMesh/MREdgeMetric.cpp +++ b/source/MRMesh/MREdgeMetric.cpp @@ -97,7 +97,7 @@ EdgeMetric edgeDihedralAngleMetric( const MeshTopology& topology, const VertCoor if ( topology.isBdEdge( e, nullptr ) ) return params.boundaryValue; - auto a = dihedralAngleSin( topology, points, e ); + auto a = dihedralAngle( topology, points, e ); return a >= 0 ? params.convexFactor * a : params.concaveFactor * a; }; } From c0960ed1f2233fb359de8d76524189352c22fc76 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Tue, 7 Apr 2026 16:06:08 +0300 Subject: [PATCH 14/15] vb --- source/MRMesh/MRGraph.cpp | 17 ++++++-- source/MRMesh/MRGraph.h | 4 ++ source/MRMesh/MRSegmentMesh.cpp | 76 +++++++++++++++++++++------------ source/MRMesh/MRSegmentMesh.h | 5 ++- 4 files changed, 69 insertions(+), 33 deletions(-) diff --git a/source/MRMesh/MRGraph.cpp b/source/MRMesh/MRGraph.cpp index 7d98e3cc7d52..ac593a6be6ee 100644 --- a/source/MRMesh/MRGraph.cpp +++ b/source/MRMesh/MRGraph.cpp @@ -5,15 +5,24 @@ namespace MR { void Graph::construct( NeighboursPerVertex neighboursPerVertex, EndsPerEdge endsPerEdge ) +{ + construct( + std::move( neighboursPerVertex ), + VertBitSet( neighboursPerVertex.size(), true ), + std::move( endsPerEdge ), + EdgeBitSet( endsPerEdge.size(), true ) + ); +} + +void Graph::construct( NeighboursPerVertex neighboursPerVertex, VertBitSet validVerts, + EndsPerEdge endsPerEdge, EdgeBitSet validEdges ) { MR_TIMER; - validVerts_.clear(); - validVerts_.resize( neighboursPerVertex.size(), true ); + validVerts_ = std::move( validVerts ); neighboursPerVertex_ = std::move( neighboursPerVertex ); - validEdges_.clear(); - validEdges_.resize( endsPerEdge.size(), true ); + validEdges_ = std::move( validEdges ); endsPerEdge_ = std::move( endsPerEdge ); assert( checkValidity() ); diff --git a/source/MRMesh/MRGraph.h b/source/MRMesh/MRGraph.h index 2f8703b06436..33d8cec6a5fd 100644 --- a/source/MRMesh/MRGraph.h +++ b/source/MRMesh/MRGraph.h @@ -48,6 +48,10 @@ class Graph /// constructs the graph from all valid vertices and edges MRMESH_API void construct( NeighboursPerVertex neighboursPerVertex, EndsPerEdge endsPerEdge ); + /// constructs the graph from given data that can contain invalid vertices and edges + MRMESH_API void construct( NeighboursPerVertex neighboursPerVertex, VertBitSet validVerts, + EndsPerEdge endsPerEdge, EdgeBitSet validEdges ); + /// returns the number of vertex records, including invalid ones [[nodiscard]] size_t vertSize() const { return neighboursPerVertex_.size(); } diff --git a/source/MRMesh/MRSegmentMesh.cpp b/source/MRMesh/MRSegmentMesh.cpp index 4044bded4c13..965bebd73521 100644 --- a/source/MRMesh/MRSegmentMesh.cpp +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -21,10 +21,12 @@ static constexpr double ProhibitMergePenalty = DBL_MAX; class MeshSegmenter { public: - MeshSegmenter( const Mesh& mesh, const EdgeMetric& curvMetric ); - GroupOrder run(); + static Expected run( const Mesh& mesh, const EdgeMetric& curvMetric, const ProgressCallback& progress ); private: + MeshSegmenter( const Mesh& mesh, const EdgeMetric& curvMetric ) : mesh_( mesh ), curvMetric_( curvMetric ) {} + Expected run_( const ProgressCallback& progress ); + double mergePenalty_( Graph::EdgeId ge ) const; void constructGraph_(); @@ -56,11 +58,21 @@ class MeshSegmenter Heap heap_; }; -MeshSegmenter::MeshSegmenter( const Mesh& mesh, const EdgeMetric& curvMetric ) - : mesh_( mesh ), curvMetric_( curvMetric ) +Expected MeshSegmenter::run( const Mesh& mesh, const EdgeMetric& curvMetric, const ProgressCallback& progress ) { - constructGraph_(); - constructHeap_(); + MR_TIMER; + + MeshSegmenter s( mesh, curvMetric ); + + s.constructGraph_(); + if ( !reportProgress( progress, 0.1f ) ) + return unexpectedOperationCanceled(); + + s.constructHeap_(); + if ( !reportProgress( progress, 0.2f ) ) + return unexpectedOperationCanceled(); + + return s.run_( subprogress( progress, 0.2f, 1.0f ) ); } inline double MeshSegmenter::mergePenalty_( Graph::EdgeId ge ) const @@ -82,38 +94,41 @@ void MeshSegmenter::constructGraph_() graphEdgeData_.resize( mesh_.topology.undirectedEdgeSize() ); Graph::EndsPerEdge ends( mesh_.topology.undirectedEdgeSize() ); - ParallelFor( graphEdgeData_, [&]( Graph::EdgeId ge ) + Graph::EdgeBitSet validGraphEdges; + static_cast( validGraphEdges ) = mesh_.topology.findNotLoneUndirectedEdges(); + BitSetParallelFor( validGraphEdges, [&]( Graph::EdgeId ge ) { UndirectedEdgeId ue( (int)ge ); - if ( mesh_.topology.isLoneEdge( ue ) ) + + Graph::EndVertices vv; + vv.v0 = Graph::VertId( (int)mesh_.topology.left( ue ) ); + vv.v1 = Graph::VertId( (int)mesh_.topology.right( ue ) ); + if ( !vv.v0 || !vv.v1 ) + { + validGraphEdges.reset( ge ); return; + } + + assert( vv.v0 != vv.v1 ); + if ( vv.v1 < vv.v0 ) + std::swap( vv.v0, vv.v1 ); + ends[ge] = vv; + const auto len = mesh_.edgeLength( ue ); graphEdgeData_[ge] = { .length = len, .lengthCurv = len * curvMetric_( ue ) }; - - Graph::EndVertices vv; - vv.v0 = Graph::VertId( (int)mesh_.topology.left( ue ) ); - vv.v1 = Graph::VertId( (int)mesh_.topology.right( ue ) ); - if ( vv.v0 && vv.v1 ) - { - assert( vv.v0 != vv.v1 ); - if ( vv.v1 < vv.v0 ) - std::swap( vv.v0, vv.v1 ); - ends[ge] = vv; - } } ); graphVertData_.resize( mesh_.topology.faceSize() ); Graph::NeighboursPerVertex neis( mesh_.topology.faceSize() ); - ParallelFor( graphVertData_, [&]( Graph::VertId gv ) + Graph::VertBitSet validGraphVerts; + static_cast( validGraphVerts ) = mesh_.topology.getValidFaces(); + BitSetParallelFor( validGraphVerts, [&]( Graph::VertId gv ) { FaceId f( (int)gv ); - if ( !mesh_.topology.hasFace( f ) ) - return; - Graph::Neighbours n; n.reserve( mesh_.topology.getFaceDegree( f ) ); for ( auto e : leftRing( mesh_.topology, f ) ) @@ -127,7 +142,7 @@ void MeshSegmenter::constructGraph_() std::sort( n.begin(), n.end() ); neis[gv] = std::move( n ); } ); - graph_.construct( std::move( neis ), std::move( ends ) ); + graph_.construct( std::move( neis ), std::move( validGraphVerts ), std::move( ends ), std::move( validGraphEdges ) ); } void MeshSegmenter::constructHeap_() @@ -168,25 +183,30 @@ std::optional MeshSegmenter::mergeNext_() return ends; } -GroupOrder MeshSegmenter::run() +Expected MeshSegmenter::run_( const ProgressCallback& progress ) { MR_TIMER; GroupOrder res; + const auto numIters = graph_.validVerts().count(); //actually -1, but not important + size_t iter = 0; while ( auto vv = mergeNext_() ) + { res.push_back( { FaceId( int( vv->v0 ) ), FaceId( int( vv->v1 ) ) } ); + if ( !reportProgress( progress, [&]{ return float( iter ) / numIters; }, ++iter, 1024 ) ) + return unexpectedOperationCanceled(); + } return res; } } //anonymous namespace -Expected segmentMesh( const Mesh& mesh, const EdgeMetric& curvMetric ) +Expected segmentMesh( const Mesh& mesh, const EdgeMetric& curvMetric, const ProgressCallback& progress ) { MR_TIMER; if ( !curvMetric ) return unexpected( "no curvMetric given" ); - MeshSegmenter s( mesh, curvMetric ); - return s.run(); + return MeshSegmenter::run( mesh, curvMetric, progress ); } UndirectedEdgeBitSet findSegmentBoundaries( const MeshTopology& topology, diff --git a/source/MRMesh/MRSegmentMesh.h b/source/MRMesh/MRSegmentMesh.h index 67ec265f9a73..07ecec36bbe1 100644 --- a/source/MRMesh/MRSegmentMesh.h +++ b/source/MRMesh/MRSegmentMesh.h @@ -7,10 +7,13 @@ namespace MR { +/// the order of segments construction starting from individual faces; +/// each pair of faces are representatives of two distinct segments to be merged together using GroupOrder = std::vector; MRMESH_API Expected segmentMesh( const Mesh& mesh, - const EdgeMetric& curvMetric ); ///< integral of this metric over segments' boundaries will be maximized + const EdgeMetric& curvMetric, ///< integral of this metric over segments' boundaries will be maximized, take e.g. edgeDihedralAngleMetric() + const ProgressCallback& progress = {} ); [[nodiscard]] MRMESH_API UndirectedEdgeBitSet findSegmentBoundaries( const MeshTopology& topology, const GroupOrder& groupOrder, int numSegments ); From 7dd7f0d2c0fe6f011b47dc2abacc94c67880b9ab Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Tue, 7 Apr 2026 16:26:44 +0300 Subject: [PATCH 15/15] comments --- source/MRMesh/MRSegmentMesh.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/MRMesh/MRSegmentMesh.h b/source/MRMesh/MRSegmentMesh.h index 07ecec36bbe1..33373362877e 100644 --- a/source/MRMesh/MRSegmentMesh.h +++ b/source/MRMesh/MRSegmentMesh.h @@ -11,10 +11,18 @@ namespace MR /// each pair of faces are representatives of two distinct segments to be merged together using GroupOrder = std::vector; +/// starting from individual faces, merge them in progressively larger segments until every connected component contains only 1 segment; +/// merge order is guided by the preferences: +/// 1) prefer merging smaller by area segments, +/// 2) prefer merging two segment with long common boundary, +/// 3) prefer merging two segments with low average value of the given curvMetric on the common boundary; +/// take e.g. edgeDihedralAngleMetric() as curvMetric MRMESH_API Expected segmentMesh( const Mesh& mesh, - const EdgeMetric& curvMetric, ///< integral of this metric over segments' boundaries will be maximized, take e.g. edgeDihedralAngleMetric() + const EdgeMetric& curvMetric, const ProgressCallback& progress = {} ); +/// executes grouping of segments till desired number of segments is reached, +/// then returns the boundary edges in between the segments [[nodiscard]] MRMESH_API UndirectedEdgeBitSet findSegmentBoundaries( const MeshTopology& topology, const GroupOrder& groupOrder, int numSegments );