diff --git a/source/MRMesh/MREdgeMetric.cpp b/source/MRMesh/MREdgeMetric.cpp index 94bfd8a576be..5727fbf26419 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 = dihedralAngle( 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/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/MRHeap.h b/source/MRMesh/MRHeap.h index 7daf5578d6d4..3ff81d9a063e 100644 --- a/source/MRMesh/MRHeap.h +++ b/source/MRMesh/MRHeap.h @@ -28,31 +28,43 @@ class Heap T val; }; + /// constructs an empty heap + 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/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..965bebd73521 --- /dev/null +++ b/source/MRMesh/MRSegmentMesh.cpp @@ -0,0 +1,244 @@ +#include "MRSegmentMesh.h" +#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 + +namespace MR +{ + +namespace +{ + +static constexpr double ProhibitMergePenalty = DBL_MAX; + +class MeshSegmenter +{ +public: + 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_(); + void constructHeap_(); + std::optional mergeNext_(); + +private: + const Mesh& mesh_; + const EdgeMetric& curvMetric_; + + Graph graph_; + + 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_; +}; + +Expected MeshSegmenter::run( const Mesh& mesh, const EdgeMetric& curvMetric, const ProgressCallback& progress ) +{ + 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 +{ + const auto & ed = graphEdgeData_[ge]; + if ( ed.length <= 0 ) + return ProhibitMergePenalty; + const auto & vv = graph_.ends( ge ); + return std::min( graphVertData_[vv.v0].area, graphVertData_[vv.v1].area ) * ed.lengthCurv / sqr( ed.length ); +} + +void MeshSegmenter::constructGraph_() +{ + MR_TIMER; + + // initially: + // Graph::VertId = Mesh::FaceId + // Graph::EdgeId = Mesh::UndirectedEdgeId + + graphEdgeData_.resize( mesh_.topology.undirectedEdgeSize() ); + Graph::EndsPerEdge ends( mesh_.topology.undirectedEdgeSize() ); + Graph::EdgeBitSet validGraphEdges; + static_cast( validGraphEdges ) = mesh_.topology.findNotLoneUndirectedEdges(); + BitSetParallelFor( validGraphEdges, [&]( Graph::EdgeId ge ) + { + UndirectedEdgeId ue( (int)ge ); + + 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 ) + }; + } ); + + graphVertData_.resize( mesh_.topology.faceSize() ); + Graph::NeighboursPerVertex neis( mesh_.topology.faceSize() ); + Graph::VertBitSet validGraphVerts; + static_cast( validGraphVerts ) = mesh_.topology.getValidFaces(); + BitSetParallelFor( validGraphVerts, [&]( Graph::VertId gv ) + { + FaceId f( (int)gv ); + Graph::Neighbours n; + n.reserve( mesh_.topology.getFaceDegree( f ) ); + 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 ); + } + graphVertData_[gv].area = mesh_.area( f ); + std::sort( n.begin(), n.end() ); + neis[gv] = std::move( n ); + } ); + graph_.construct( std::move( neis ), std::move( validGraphVerts ), std::move( ends ), std::move( validGraphEdges ) ); +} + +void MeshSegmenter::constructHeap_() +{ + MR_TIMER; + + 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 = mergePenalty_( ge ); + } ); + heap_ = Heap( std::move( elements ) ); +} + +std::optional MeshSegmenter::mergeNext_() +{ + const auto [ge, penalty] = heap_.top(); + if ( penalty == ProhibitMergePenalty ) + return std::nullopt; + assert( graph_.valid( ge ) ); + assert( penalty == mergePenalty_( ge ) ); + heap_.setSmallerValue( ge, ProhibitMergePenalty ); + + auto ends = graph_.ends( ge ); + graphVertData_[ends.v0] += graphVertData_[ends.v1]; + graph_.merge( ends.v0, ends.v1, [&]( Graph::EdgeId remnant, Graph::EdgeId dead ) + { + graphEdgeData_[remnant] += graphEdgeData_[dead]; + heap_.setSmallerValue( dead, ProhibitMergePenalty ); + } ); + + for ( auto ne : graph_.neighbours( ends.v0 ) ) + heap_.setValue( ne, mergePenalty_( ne ) ); + + return ends; +} + +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, const ProgressCallback& progress ) +{ + MR_TIMER; + if ( !curvMetric ) + return unexpected( "no curvMetric given" ); + + return MeshSegmenter::run( mesh, curvMetric, progress ); +} + +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 new file mode 100644 index 000000000000..33373362877e --- /dev/null +++ b/source/MRMesh/MRSegmentMesh.h @@ -0,0 +1,29 @@ +#pragma once + +#include "MRMeshFwd.h" +#include "MRExpected.h" +#include "MRFaceFace.h" + +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; + +/// 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, + 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 ); + +} //namespace MR