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; } ///