diff --git a/Triangle.NET/TestApp/FormMain.cs b/Triangle.NET/TestApp/FormMain.cs index 08bde6f..13e8f2f 100644 --- a/Triangle.NET/TestApp/FormMain.cs +++ b/Triangle.NET/TestApp/FormMain.cs @@ -592,11 +592,11 @@ namespace MeshExplorer UpdateLog(); } - private void CreateVoronoi() + private bool CreateVoronoi() { if (mesh == null) { - return; + return false; } if (mesh.IsPolygon) @@ -616,7 +616,7 @@ namespace MeshExplorer DarkMessageBox.Show("Exception - Bounded Voronoi", ex.Message, MessageBoxButtons.OK); } - this.voronoi = null; + return false; } } else @@ -624,12 +624,11 @@ namespace MeshExplorer this.voronoi = new StandardVoronoi(mesh); } - if (this.voronoi != null) - { - // HACK: List -> ICollection ? Nope, no way. - // Vertex[] -> ICollection ? Well, ok. - renderManager.Set(voronoi.Vertices.ToArray(), voronoi.Edges, false); - } + // HACK: List -> ICollection ? Nope, no way. + // Vertex[] -> ICollection ? Well, ok. + renderManager.Set(voronoi.Vertices.ToArray(), voronoi.Edges, false); + + return true; } private void ShowLog() @@ -722,8 +721,7 @@ namespace MeshExplorer { if (this.voronoi == null) { - CreateVoronoi(); - menuViewVoronoi.Checked = true; + menuViewVoronoi.Checked = CreateVoronoi(); } else { diff --git a/Triangle.NET/Triangle.Rendering/ColorManager.cs b/Triangle.NET/Triangle.Rendering/ColorManager.cs index 713e9b9..b3d2b20 100644 --- a/Triangle.NET/Triangle.Rendering/ColorManager.cs +++ b/Triangle.NET/Triangle.Rendering/ColorManager.cs @@ -167,12 +167,13 @@ namespace TriangleNet.Rendering // Change or add as many colors as you like... private static Color[] regionColors = { - Color.FromArgb(200, 0, 0, 255), + Color.Transparent, + Color.FromArgb(200, 0, 255, 0), Color.FromArgb(200, 255, 0, 0), + Color.FromArgb(200, 0, 0, 255), Color.FromArgb(200, 0, 255, 255), Color.FromArgb(200, 255, 255, 0), Color.FromArgb(200, 255, 0, 255), - Color.FromArgb(200, 0, 255, 0), Color.FromArgb(200, 127, 0, 255), Color.FromArgb(200, 0, 127, 255) }; diff --git a/Triangle.NET/Triangle.Rendering/GDI/ImageRenderer.cs b/Triangle.NET/Triangle.Rendering/GDI/ImageRenderer.cs new file mode 100644 index 0000000..3bd3c8e --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/ImageRenderer.cs @@ -0,0 +1,150 @@ + +namespace TriangleNet.Rendering.GDI +{ + using System; + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Imaging; + using System.IO; + + public class ImageRenderer + { + ColorManager colors = ImageRenderer.LightScheme(); + + public ColorManager ColorScheme + { + get { return colors; } + set { colors = value; } + } + + public bool EnableRegions { get; set; } + + public bool EnablePoints { get; set; } + + /// + /// Export the mesh to PNG format. + /// + /// The current mesh. + /// The desired width (pixel) of the image. + /// The PNG filename. + /// Enable rendering of regions. + /// Enable rendering of points. + public static void Save(Mesh mesh, string file = null, int width = 800, + bool regions = false, bool points = true) + { + // Check file name + if (String.IsNullOrWhiteSpace(file)) + { + file = String.Format("mesh-{0}.png", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss")); + } + + // Ensure .png extension. + if (file.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) + { + Path.ChangeExtension(file, ".png"); + } + + var renderer = new ImageRenderer(); + + renderer.EnableRegions = regions; + renderer.EnablePoints = points; + + var bitmap = renderer.Render(mesh, width); + + bitmap.Save(file, ImageFormat.Png); + } + + /// + /// Export the mesh to PNG format. + /// + /// The current mesh. + /// The desired width (pixel) of the image. + /// The bitmap. + /// + /// The width has to be at least 2 * sqrt(n), n the number of vertices. + /// Otherwise, an empty bitmap + /// + public Bitmap Render(Mesh mesh, int width = 800) + { + Bitmap bitmap; + + // Check if the specified width is reasonable + if (width < 2 * Math.Sqrt(mesh.Vertices.Count)) + { + return new Bitmap(1, 1); + } + + var bounds = mesh.Bounds; + + // World margin on each side + float margin = (float)bounds.Height * 0.05f; + float scale = width / ((float)bounds.Width + 2 * margin); + + var target = new Rectangle(0, 0, width, (int)((bounds.Height + 2 * margin) * scale)); + + bitmap = new Bitmap(width, target.Height, PixelFormat.Format32bppPArgb); + + using (var g = Graphics.FromImage(bitmap)) + { + g.Clear(colors.Background); + g.SmoothingMode = SmoothingMode.HighQuality; + + var context = new RenderContext(new Projection(target), colors); + context.Add(mesh, true); + + if (EnableRegions) + { + context.Add(GetRegions(mesh)); + } + + if (!EnablePoints) + { + context.Enable(3, false); + } + + var renderer = new LayerRenderer(); + renderer.Context = context; + renderer.RenderTarget = g; + renderer.Render(); + } + + return bitmap; + } + + private int[] GetRegions(Mesh mesh) + { + mesh.Renumber(); + + var labels = new int[mesh.Triangles.Count]; + var regions = new HashSet(); + + foreach (var t in mesh.Triangles) + { + labels[t.ID] = t.Label; + regions.Add(t.Label); + } + + if (colors.ColorDictionary == null) + { + colors.CreateColorDictionary(regions.Count); + } + + return labels; + } + + public static ColorManager LightScheme() + { + var colors = new ColorManager(); + + colors.Background = Color.White; + colors.Point = new SolidBrush(Color.FromArgb(60, 80, 120)); + colors.SteinerPoint = new SolidBrush(Color.DarkGreen); + colors.Line = new Pen(Color.FromArgb(200, 200, 200)); + colors.Segment = new Pen(Color.SteelBlue); + colors.VoronoiLine = new Pen(Color.FromArgb(160, 170, 180)); + + return colors; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj b/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj index f0b5bae..f9ad27f 100644 --- a/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj +++ b/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj @@ -50,6 +50,7 @@ + diff --git a/Triangle.NET/Triangle/Geometry/Contour.cs b/Triangle.NET/Triangle/Geometry/Contour.cs index 6cd6ae6..d4a9f6a 100644 --- a/Triangle.NET/Triangle/Geometry/Contour.cs +++ b/Triangle.NET/Triangle/Geometry/Contour.cs @@ -76,8 +76,8 @@ namespace TriangleNet.Geometry /// /// Try to find a point inside the contour. /// - /// The number of iterations on each segment (default = 8). - /// Threshold for co-linear points (default = 2e-6). + /// The number of iterations on each segment (default = 5). + /// Threshold for co-linear points (default = 2e-5). /// Point inside the contour /// Throws if no point could be found. /// @@ -86,16 +86,16 @@ namespace TriangleNet.Geometry /// on the bisecting line, or, if is less than /// eps, on the perpendicular line. /// A given number of points will be tested (limit), while the distance to the contour - /// boundary will be reduced in each iteration (with a factor of 1/2^i, i = 1 ... limit). + /// boundary will be reduced in each iteration (with a factor 1 / 2^i, i = 1 ... limit). /// - public Point FindInteriorPoint(int limit = 8, double eps = 2e-6) + public Point FindInteriorPoint(int limit = 5, double eps = 2e-5) { - var point = new Point(0.0, 0.0); - if (convex) { int count = this.Points.Count; + var point = new Point(0.0, 0.0); + for (int i = 0; i < count; i++) { point.x += this.Points[i].x; @@ -152,8 +152,8 @@ namespace TriangleNet.Geometry c = contour[(i + 2) % length]; // Corner point. - bx = b.X; - by = b.Y; + bx = b.x; + by = b.y; // NOTE: if we knew the contour points were in counterclockwise order, we // could skip concave corners and search only in one direction. @@ -163,14 +163,14 @@ namespace TriangleNet.Geometry if (Math.Abs(h) < eps) { // Points are nearly co-linear. Use perpendicular direction. - dx = (c.Y - a.Y) / 2; - dy = (a.X - c.X) / 2; + dx = (c.y - a.y) / 2; + dy = (a.x - c.x) / 2; } else { // Direction [midpoint(a-c) -> corner point] - dx = (a.X + c.X) / 2 - bx; - dy = (a.Y + c.Y) / 2 - by; + dx = (a.x + c.x) / 2 - bx; + dy = (a.y + c.y) / 2 - by; } // Move around the contour. @@ -182,8 +182,8 @@ namespace TriangleNet.Geometry for (int j = 0; j < limit; j++) { // Search in direction. - test.X = bx + dx / h; - test.Y = by + dy / h; + test.x = bx + dx * h; + test.y = by + dy * h; if (bounds.Contains(test) && IsPointInPolygon(test, contour)) { @@ -191,15 +191,15 @@ namespace TriangleNet.Geometry } // Search in opposite direction (see NOTE above). - test.X = bx - dx / h; - test.Y = by - dy / h; + test.x = bx - dx * h; + test.y = by - dy * h; if (bounds.Contains(test) && IsPointInPolygon(test, contour)) { return test; } - h = 2.0 * h; + h = h / 2; } } diff --git a/Triangle.NET/Triangle/Geometry/Point.cs b/Triangle.NET/Triangle/Geometry/Point.cs index f71061e..3fa644d 100644 --- a/Triangle.NET/Triangle/Geometry/Point.cs +++ b/Triangle.NET/Triangle/Geometry/Point.cs @@ -23,7 +23,7 @@ namespace TriangleNet.Geometry #endif public Point() - : this(0, 0, 0) + : this(0.0, 0.0, 0) { } diff --git a/Triangle.NET/Triangle/Geometry/Segment.cs b/Triangle.NET/Triangle/Geometry/Segment.cs index 78e5a17..c521709 100644 --- a/Triangle.NET/Triangle/Geometry/Segment.cs +++ b/Triangle.NET/Triangle/Geometry/Segment.cs @@ -18,6 +18,30 @@ namespace TriangleNet.Geometry int label; + /// + /// Gets or sets the segments boundary mark. + /// + public int Label + { + get { return label; } + set { label = value; } + } + /// + /// Gets the first endpoints index. + /// + public int P0 + { + get { return v0.id; } + } + + /// + /// Gets the second endpoints index. + /// + public int P1 + { + get { return v1.id; } + } + /// /// Initializes a new instance of the class. /// @@ -64,30 +88,5 @@ namespace TriangleNet.Geometry { throw new NotImplementedException(); } - - /// - /// Gets the first endpoints index. - /// - public int P0 - { - get { return v0.id; } - } - - /// - /// Gets the second endpoints index. - /// - public int P1 - { - get { return v1.id; } - } - - /// - /// Gets or sets the segments boundary mark. - /// - public int Label - { - get { return label; } - set { label = value; } - } } } diff --git a/Triangle.NET/Triangle/IO/TriangleFormat.cs b/Triangle.NET/Triangle/IO/TriangleFormat.cs index 0c50d27..9fd7965 100644 --- a/Triangle.NET/Triangle/IO/TriangleFormat.cs +++ b/Triangle.NET/Triangle/IO/TriangleFormat.cs @@ -19,7 +19,7 @@ namespace TriangleNet.IO { public bool IsSupported(string file) { - string ext = Path.GetExtension(file); + string ext = Path.GetExtension(file).ToLower(); if (ext == ".node" || ext == ".poly" || ext == ".ele") { diff --git a/Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs b/Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs new file mode 100644 index 0000000..189e1d3 --- /dev/null +++ b/Triangle.NET/Triangle/Meshing/Iterators/VertexCirculator.cs @@ -0,0 +1,104 @@ + +namespace TriangleNet.Meshing.Iterators +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Topology; + + public class VertexCirculator + { + List cache = new List(); + + Mesh mesh; + + public VertexCirculator(Mesh mesh) + { + this.mesh = mesh; + + mesh.MakeVertexMap(); + } + + /// + /// Enumerate all vertices adjacent to given vertex. + /// + /// The center vertex. + /// + public IEnumerable EnumerateVertices(Vertex vertex) + { + BuildCache(vertex, true); + + foreach (var item in cache) + { + yield return item.Dest(); + } + } + + /// + /// Enumerate all triangles adjacent to given vertex. + /// + /// The center vertex. + /// + public IEnumerable EnumerateTriangles(Vertex vertex) + { + BuildCache(vertex, false); + + foreach (var item in cache) + { + yield return item.tri; + } + } + + private void BuildCache(Vertex vertex, bool vertices) + { + cache.Clear(); + + Otri init = vertex.tri; + Otri next = default(Otri); + Otri prev = default(Otri); + + init.Copy(ref next); + + // Move counter-clockwise around the vertex. + while (next.tri.id != Mesh.DUMMY) + { + cache.Add(next); + + next.Copy(ref prev); + next.Onext(); + + if (next.Equals(init)) + { + break; + } + } + + if (next.tri.id == Mesh.DUMMY) + { + // We reached the boundary. To get all adjacent triangles, start + // again at init triangle and now move clockwise. + init.Copy(ref next); + + if (vertices) + { + // Don't forget to add the vertex lying on the boundary. + prev.Lnext(); + cache.Add(prev); + } + + next.Oprev(); + + while (next.tri.id != Mesh.DUMMY) + { + cache.Insert(0, next); + + next.Oprev(); + + if (next.Equals(init)) + { + break; + } + } + } + } + } +} diff --git a/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs b/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs index 5c4094a..718a380 100644 --- a/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs +++ b/Triangle.NET/Triangle/Smoothing/SimpleSmoother.cs @@ -20,7 +20,8 @@ namespace TriangleNet.Smoothing /// public class SimpleSmoother : ISmoother { - IPredicates predicates; + TrianglePool pool; + Configuration config; IVoronoiFactory factory; @@ -30,19 +31,34 @@ namespace TriangleNet.Smoothing /// Initializes a new instance of the class. /// public SimpleSmoother() - : this(new VoronoiFactory(), RobustPredicates.Default) + : this(new VoronoiFactory()) { } /// /// Initializes a new instance of the class. /// - /// Voronoi object factory. - /// Geometric predicates implementation. - public SimpleSmoother(IVoronoiFactory factory, IPredicates predicates) + public SimpleSmoother(IVoronoiFactory factory) { this.factory = factory; - this.predicates = predicates; + this.pool = new TrianglePool(); + + this.config = new Configuration( + () => RobustPredicates.Default, + () => pool.Restart()); + + this.options = new ConstraintOptions() { ConformingDelaunay = true }; + } + + /// + /// Initializes a new instance of the class. + /// + /// Voronoi object factory. + /// Configuration. + public SimpleSmoother(IVoronoiFactory factory, Configuration config) + { + this.factory = factory; + this.config = config; this.options = new ConstraintOptions() { ConformingDelaunay = true }; } @@ -56,18 +72,21 @@ namespace TriangleNet.Smoothing { var smoothedMesh = (Mesh)mesh; + var mesher = new GenericMesher(config); + var predicates = config.Predicates(); + // 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++) { - Step(smoothedMesh, factory); + Step(smoothedMesh, factory, predicates); - // Actually, we only want to rebuild, if mesh is no longer + // Actually, we only want to rebuild, if the mesh is no longer // Delaunay. Flipping edges could be the right choice instead // of re-triangulating... - smoothedMesh = (Mesh)Rebuild(smoothedMesh).Triangulate(options); + smoothedMesh = (Mesh)mesher.Triangulate(Rebuild(smoothedMesh), options); factory.Reset(); } @@ -75,7 +94,7 @@ namespace TriangleNet.Smoothing smoothedMesh.CopyTo((Mesh)mesh); } - private void Step(Mesh mesh, IVoronoiFactory factory) + private void Step(Mesh mesh, IVoronoiFactory factory, IPredicates predicates) { var voronoi = new BoundedVoronoi(mesh, factory, predicates); diff --git a/Triangle.NET/Triangle/Tools/Interpolation.cs b/Triangle.NET/Triangle/Tools/Interpolation.cs index 6fa6410..f315b03 100644 --- a/Triangle.NET/Triangle/Tools/Interpolation.cs +++ b/Triangle.NET/Triangle/Tools/Interpolation.cs @@ -40,11 +40,10 @@ namespace TriangleNet.Tools dx = vertex.x - org.x; dy = vertex.y - org.y; - // To interpolate vertex attributes for the new vertex inserted at - // the circumcenter, define a coordinate system with a xi-axis, - // directed from the triangle's origin to its destination, and - // an eta-axis, directed from its origin to its apex. - // Calculate the xi and eta coordinates of the circumcenter. + // To interpolate vertex attributes for the new vertex, define a + // coordinate system with a xi-axis directed from the triangle's + // origin to its destination, and an eta-axis, directed from its + // origin to its apex. xi = (yao * dx - xao * dy) * (2.0 * denominator); eta = (xdo * dy - ydo * dx) * (2.0 * denominator); diff --git a/Triangle.NET/Triangle/Triangle.csproj b/Triangle.NET/Triangle/Triangle.csproj index d3a23f5..73ce7cf 100644 --- a/Triangle.NET/Triangle/Triangle.csproj +++ b/Triangle.NET/Triangle/Triangle.csproj @@ -71,6 +71,7 @@ + diff --git a/Triangle.NET/Triangle/TrianglePool.cs b/Triangle.NET/Triangle/TrianglePool.cs index 331d70b..2a7c0bf 100644 --- a/Triangle.NET/Triangle/TrianglePool.cs +++ b/Triangle.NET/Triangle/TrianglePool.cs @@ -298,7 +298,7 @@ namespace TriangleNet public void Reset() { - index = 0; + index = offset = 0; } } }