diff --git a/source/MRMesh/MRMesh.vcxproj b/source/MRMesh/MRMesh.vcxproj
index 8e948f8493a3..ccc6bd59c3cd 100644
--- a/source/MRMesh/MRMesh.vcxproj
+++ b/source/MRMesh/MRMesh.vcxproj
@@ -257,6 +257,8 @@
+
+
@@ -459,6 +461,7 @@
+
diff --git a/source/MRMesh/MRMesh.vcxproj.filters b/source/MRMesh/MRMesh.vcxproj.filters
index 143b3846d8cd..886fc545462d 100644
--- a/source/MRMesh/MRMesh.vcxproj.filters
+++ b/source/MRMesh/MRMesh.vcxproj.filters
@@ -604,7 +604,7 @@
Source Files\AABBTree
- Source Files\PointCloud
+ Source Files\Relax
Source Files\Math
@@ -733,7 +733,7 @@
Source Files\Mesh
- Source Files\MeshAlgorithm
+ Source Files\Relax
Source Files\MeshAlgorithm
@@ -843,6 +843,12 @@
Source Files\History
+
+ Source Files\Relax
+
+
+ Source Files\Relax
+
@@ -1427,6 +1433,9 @@
Source Files\Polyline
+
+ Source Files\Polyline
+
diff --git a/source/MRMesh/MRMeshRelax.h b/source/MRMesh/MRMeshRelax.h
index f9b52723128a..6f89d55d9145 100644
--- a/source/MRMesh/MRMeshRelax.h
+++ b/source/MRMesh/MRMeshRelax.h
@@ -1,6 +1,7 @@
#pragma once
#include "MRMeshFwd.h"
#include "MRProgressCallback.h"
+#include "MRRelaxParams.h"
namespace MR
{
@@ -9,16 +10,6 @@ namespace MR
/// \ingroup MeshAlgorithmGroup
/// \{
-struct RelaxParams
-{
- /// number of iterations
- int iterations{ 1 };
- /// region to relax
- const VertBitSet* region{ nullptr };
- /// speed of relaxing, typical values (0.0, 0.5]
- float force{ 0.5f };
-};
-
struct MeshRelaxParams : RelaxParams
{
/// smooth tetrahedron verts (with complete three edges ring) to base triangle (based on its edges destinations)
@@ -34,12 +25,6 @@ MRMESH_API bool relax( Mesh& mesh, const MeshRelaxParams& params = {}, ProgressC
/// \return true if was finished successfully, false if was interrupted by progress callback
MRMESH_API bool relaxKeepVolume( Mesh& mesh, const MeshRelaxParams& params = {}, ProgressCallback cb = {} );
-enum class RelaxApproxType
-{
- Planar,
- Quadric
-};
-
struct MeshApproxRelaxParams : MeshRelaxParams
{
/// radius to find neighbors by surface
diff --git a/source/MRMesh/MRPointCloudRelax.h b/source/MRMesh/MRPointCloudRelax.h
index 89ea45e4404e..21488a75ac5b 100644
--- a/source/MRMesh/MRPointCloudRelax.h
+++ b/source/MRMesh/MRPointCloudRelax.h
@@ -1,6 +1,7 @@
#pragma once
#include "MRMeshFwd.h"
-#include "MRMeshRelax.h"
+#include "MRProgressCallback.h"
+#include "MRRelaxParams.h"
namespace MR
{
diff --git a/source/MRMesh/MRPolylineRelax.cpp b/source/MRMesh/MRPolylineRelax.cpp
new file mode 100644
index 000000000000..783e84c1da49
--- /dev/null
+++ b/source/MRMesh/MRPolylineRelax.cpp
@@ -0,0 +1,130 @@
+#include "MRPolylineRelax.h"
+#include "MRBitSetParallelFor.h"
+#include "MRPolyline.h"
+#include "MRTimer.h"
+#include "MRVector2.h"
+#include "MRWriter.h"
+
+namespace MR
+{
+
+template
+bool relax( Polyline &polyline, const RelaxParams ¶ms, ProgressCallback cb )
+{
+ if ( params.iterations <= 0 )
+ return true;
+
+ MR_TIMER
+ MR_WRITER(polyline)
+
+ Vector newPoints;
+ const auto& zone = polyline.topology.getVertIds( params.region );
+
+ bool keepGoing = true;
+ for ( int i = 0; i < params.iterations; ++i )
+ {
+ ProgressCallback internalCb;
+ if ( cb )
+ {
+ internalCb = [&]( float p )
+ {
+ return cb(( float( i ) + p ) / float( params.iterations ));
+ };
+ }
+
+ newPoints = polyline.points;
+ keepGoing = BitSetParallelFor( zone, [&]( VertId v )
+ {
+ auto e0 = polyline.topology.edgeWithOrg( v );
+ assert( !polyline.topology.isLoneEdge( e0 ) );
+ auto e1 = polyline.topology.next( e0 );
+ if ( e0 == e1 )
+ return;
+
+ auto mp = ( polyline.destPnt( e0 ) + polyline.destPnt( e1 ) ) / 2.f;
+
+ auto& np = newPoints[v];
+ auto pushForce = params.force * ( mp - np );
+ np += pushForce;
+ }, internalCb );
+ polyline.points.swap( newPoints );
+ if ( !keepGoing )
+ break;
+ }
+ return keepGoing;
+}
+
+template
+bool relaxKeepArea( Polyline &polyline, const RelaxParams ¶ms, ProgressCallback cb )
+{
+ if ( params.iterations <= 0 )
+ return true;
+
+ MR_TIMER
+ MR_WRITER(polyline)
+
+ Vector newPoints;
+ const auto& zone = polyline.topology.getVertIds( params.region );
+ std::vector vertPushForces( zone.size() );
+
+ bool keepGoing = true;
+ for ( int i = 0; i < params.iterations; ++i )
+ {
+ ProgressCallback internalCb1, internalCb2;
+ if ( cb )
+ {
+ internalCb1 = [&] ( float p )
+ {
+ return cb( ( float( i ) + p * 0.5f ) / float( params.iterations ) );
+ };
+ internalCb2 = [&] ( float p )
+ {
+ return cb( ( float( i ) + p * 0.5f + 0.5f ) / float( params.iterations ) );
+ };
+ }
+
+ keepGoing = BitSetParallelFor( zone, [&]( VertId v )
+ {
+ auto e0 = polyline.topology.edgeWithOrg( v );
+ assert( !polyline.topology.isLoneEdge( e0 ) );
+ auto e1 = polyline.topology.next( e0 );
+ if ( e0 == e1 )
+ return;
+
+ auto mp = ( polyline.destPnt( e0 ) + polyline.destPnt( e1 ) ) / 2.f;
+
+ vertPushForces[v] = params.force * ( mp - polyline.points[v] );
+ }, internalCb1 );
+ if ( !keepGoing )
+ break;
+
+ newPoints = polyline.points;
+ keepGoing = BitSetParallelFor( zone, [&]( VertId v )
+ {
+ auto e0 = polyline.topology.edgeWithOrg( v );
+ assert( !polyline.topology.isLoneEdge( e0 ) );
+ auto e1 = polyline.topology.next( e0 );
+ if ( e0 == e1 )
+ return;
+
+ auto& np = newPoints[v];
+ np += vertPushForces[v];
+ auto modifier = 1.0f / 2.0f;
+ np -= ( vertPushForces[polyline.topology.dest( e0 )] * modifier );
+ np -= ( vertPushForces[polyline.topology.dest( e1 )] * modifier );
+ }, internalCb2 );
+ polyline.points.swap( newPoints );
+ if ( !keepGoing )
+ break;
+ }
+
+ return keepGoing;
+}
+
+template bool relax<>( Polyline2& polyline, const RelaxParams& params, ProgressCallback cb );
+template bool relax<>( Polyline3& polyline, const RelaxParams& params, ProgressCallback cb );
+
+template bool relaxKeepArea<>( Polyline2 &polyline, const RelaxParams ¶ms, ProgressCallback cb );
+template bool relaxKeepArea<>( Polyline3 &polyline, const RelaxParams ¶ms, ProgressCallback cb );
+
+} // namespace MR
\ No newline at end of file
diff --git a/source/MRMesh/MRPolylineRelax.h b/source/MRMesh/MRPolylineRelax.h
new file mode 100644
index 000000000000..d182433f96c4
--- /dev/null
+++ b/source/MRMesh/MRPolylineRelax.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "MRMeshFwd.h"
+#include "MRProgressCallback.h"
+#include "MRRelaxParams.h"
+
+namespace MR
+{
+
+/// \addtogroup PolylineGroup
+/// \{
+
+/// applies given number of relaxation iterations to the whole polyline ( or some region if it is specified )
+/// \return true if was finished successfully, false if was interrupted by progress callback
+template
+MRMESH_API bool relax( Polyline& polyline, const RelaxParams& params = {}, ProgressCallback cb = {} );
+
+/// applies given number of relaxation iterations to the whole polyline ( or some region if it is specified )
+/// do not really keeps area but tries hard
+/// \return true if was finished successfully, false if was interrupted by progress callback
+template
+MRMESH_API bool relaxKeepArea( Polyline& polyline, const RelaxParams& params = {}, ProgressCallback cb = {} );
+
+/// \}
+
+} // namespace MR
diff --git a/source/MRMesh/MRRelaxParams.h b/source/MRMesh/MRRelaxParams.h
new file mode 100644
index 000000000000..e3a764156901
--- /dev/null
+++ b/source/MRMesh/MRRelaxParams.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "MRMeshFwd.h"
+
+namespace MR
+{
+
+struct RelaxParams
+{
+ /// number of iterations
+ int iterations{1};
+ /// region to relax
+ const VertBitSet *region{nullptr};
+ /// speed of relaxing, typical values (0.0, 0.5]
+ float force{0.5f};
+};
+
+enum class RelaxApproxType
+{
+ Planar,
+ Quadric,
+};
+
+} // namespace MR
\ No newline at end of file