diff --git a/src/Triangle.Examples/Examples/Example4.cs b/src/Triangle.Examples/Examples/Example4.cs
index 252b4a6..270054d 100644
--- a/src/Triangle.Examples/Examples/Example4.cs
+++ b/src/Triangle.Examples/Examples/Example4.cs
@@ -68,7 +68,7 @@ namespace TriangleNet.Examples
var smoother = new SimpleSmoother();
// Smooth mesh.
- smoother.Smooth(mesh, 25);
+ smoother.Smooth(mesh, 25, .05);
return mesh;
}
diff --git a/src/Triangle.Tests/Smoothing/SimpleSmootherTest.cs b/src/Triangle.Tests/Smoothing/SimpleSmootherTest.cs
index 318cb87..a969a68 100644
--- a/src/Triangle.Tests/Smoothing/SimpleSmootherTest.cs
+++ b/src/Triangle.Tests/Smoothing/SimpleSmootherTest.cs
@@ -38,7 +38,7 @@ namespace TriangleNet.Tests.Smoothing
var smoother = new SimpleSmoother();
// Smooth mesh.
- smoother.Smooth(mesh, 25);
+ Assert.IsTrue(smoother.Smooth(mesh, 25) > 0);
}
private Polygon GetPolygon()
diff --git a/src/Triangle/Smoothing/ISmoother.cs b/src/Triangle/Smoothing/ISmoother.cs
deleted file mode 100644
index 623af88..0000000
--- a/src/Triangle/Smoothing/ISmoother.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-
-namespace TriangleNet.Smoothing
-{
- using TriangleNet.Meshing;
-
- ///
- /// Interface for mesh smoothers.
- ///
- public interface ISmoother
- {
- ///
- /// Smooth mesh with 10 rounds of Voronoi iteration.
- ///
- /// The mesh.
- void Smooth(IMesh mesh);
-
- ///
- /// Smooth mesh with 10 rounds of Voronoi iteration.
- ///
- /// The mesh.
- /// The number of iterations.
- void Smooth(IMesh mesh, int limit);
- }
-}
\ No newline at end of file
diff --git a/src/Triangle/Smoothing/SimpleSmoother.cs b/src/Triangle/Smoothing/SimpleSmoother.cs
index 946aab6..acbd06e 100644
--- a/src/Triangle/Smoothing/SimpleSmoother.cs
+++ b/src/Triangle/Smoothing/SimpleSmoother.cs
@@ -4,6 +4,8 @@
//
// -----------------------------------------------------------------------
+using System;
+
namespace TriangleNet.Smoothing
{
using TriangleNet.Geometry;
@@ -18,8 +20,9 @@ namespace TriangleNet.Smoothing
/// Vertices which should not move (e.g. segment vertices) MUST have a
/// boundary mark greater than 0.
///
- public class SimpleSmoother : ISmoother
+ public class SimpleSmoother
{
+
TrianglePool pool;
Configuration config;
@@ -63,15 +66,27 @@ namespace TriangleNet.Smoothing
this.options = new ConstraintOptions() { ConformingDelaunay = true };
}
- ///
- public void Smooth(IMesh mesh)
+ ///
+ /// Smooth mesh with a maximum given number of rounds of Voronoi
+ /// iteration.
+ ///
+ /// The mesh.
+ /// The maximum number of iterations. If
+ /// non-positive, no iteration is applied at all.
+ /// The desired tolerance on the result. At each
+ /// iteration, the maximum movement by any side is considered, both for
+ /// the previous and the current solutions. If their relative difference
+ /// is not greater than the tolerance, the current solution is
+ /// considered good enough already.
+ /// The number of actual iterations performed. It is 0 if a
+ /// non-positive limit is passed. Otherwise, it is always a value
+ /// between 1 and the limit (inclusive).
+ ///
+ public int Smooth(IMesh mesh, int limit = 10, double tol = .01)
{
- Smooth(mesh, 10);
- }
+ if (limit <= 0)
+ return 0;
- ///
- public void Smooth(IMesh mesh, int limit)
- {
var smoothedMesh = (Mesh)mesh;
var mesher = new GenericMesher(config);
@@ -80,10 +95,19 @@ namespace TriangleNet.Smoothing
// The smoother should respect the mesh segment splitting behavior.
this.options.SegmentSplitting = smoothedMesh.behavior.NoBisect;
- // Take a few smoothing rounds (Lloyd's algorithm).
- for (int i = 0; i < limit; i++)
+ // The maximum distances moved from any site at the previous and
+ // current iterations.
+ double
+ prevMax = Double.PositiveInfinity,
+ currMax = Step(smoothedMesh, factory, predicates);
+ // Take a few smoothing rounds (Lloyd's algorithm). The stop
+ // criteria are the maximum number of iterations and the convergence
+ // criterion.
+ int i = 1;
+ while (i < limit && Math.Abs(currMax - prevMax) > tol * currMax)
{
- Step(smoothedMesh, factory, predicates);
+ prevMax = currMax;
+ currMax = Step(smoothedMesh, factory, predicates);
// Actually, we only want to rebuild, if the mesh is no longer
// Delaunay. Flipping edges could be the right choice instead
@@ -91,16 +115,20 @@ namespace TriangleNet.Smoothing
smoothedMesh = (Mesh)mesher.Triangulate(Rebuild(smoothedMesh), options);
factory.Reset();
+
+ i++;
}
smoothedMesh.CopyTo((Mesh)mesh);
+
+ return i;
}
- private void Step(Mesh mesh, IVoronoiFactory factory, IPredicates predicates)
+ private double Step(Mesh mesh, IVoronoiFactory factory, IPredicates predicates)
{
var voronoi = new BoundedVoronoi(mesh, factory, predicates);
- double x, y;
+ double x, y, maxDistanceMoved = 0;
foreach (var face in voronoi.Faces)
{
@@ -108,10 +136,20 @@ namespace TriangleNet.Smoothing
{
Centroid(face, out x, out y);
+ double
+ xShift = face.generator.x - x,
+ yShift = face.generator.y - y,
+ distanceMoved = Math.Sqrt(xShift * xShift + yShift * yShift);
+ if (distanceMoved > maxDistanceMoved)
+ maxDistanceMoved = distanceMoved;
+
face.generator.x = x;
face.generator.y = y;
}
}
+
+ // The maximum distance moved from any site.
+ return maxDistanceMoved;
}
///