diff --git a/Triangle.NET/MeshRenderer.Core/GDI/RenderControl.cs b/Triangle.NET/MeshRenderer.Core/GDI/RenderControl.cs index bf64193..6d8e0c9 100644 --- a/Triangle.NET/MeshRenderer.Core/GDI/RenderControl.cs +++ b/Triangle.NET/MeshRenderer.Core/GDI/RenderControl.cs @@ -77,11 +77,8 @@ namespace MeshRenderer.Core.GDI get { return showVoronoi; } set { - if (showVoronoi != value) - { - this.Render(); - } showVoronoi = value; + this.Render(); } } @@ -90,11 +87,8 @@ namespace MeshRenderer.Core.GDI get { return showRegions; } set { - if (showRegions != value) - { - this.Render(); - } showRegions = value; + this.Render(); } } diff --git a/Triangle.NET/TestApp/FormGenerator.cs b/Triangle.NET/TestApp/FormGenerator.cs index eaee467..bd2497a 100644 --- a/Triangle.NET/TestApp/FormGenerator.cs +++ b/Triangle.NET/TestApp/FormGenerator.cs @@ -22,8 +22,8 @@ namespace MeshExplorer if (currentGenerator.ParameterCount > 0) { sliderParam1.Enabled = true; - lbParam1.Text = currentGenerator.ParameterDescription(1); - lbParam1Val.Text = currentGenerator.ParameterDescription(1, sliderParam1.Value); + lbParam1.Text = currentGenerator.ParameterDescription(0); + lbParam1Val.Text = currentGenerator.ParameterDescription(0, sliderParam1.Value); } else { @@ -35,8 +35,8 @@ namespace MeshExplorer if (currentGenerator.ParameterCount > 1) { sliderParam2.Enabled = true; - lbParam2.Text = currentGenerator.ParameterDescription(2); - lbParam2Val.Text = currentGenerator.ParameterDescription(2, sliderParam2.Value); + lbParam2.Text = currentGenerator.ParameterDescription(1); + lbParam2Val.Text = currentGenerator.ParameterDescription(1, sliderParam2.Value); } else { @@ -48,8 +48,8 @@ namespace MeshExplorer if (currentGenerator.ParameterCount > 2) { sliderParam3.Enabled = true; - lbParam3.Text = currentGenerator.ParameterDescription(3); - lbParam3Val.Text = currentGenerator.ParameterDescription(3, sliderParam3.Value); + lbParam3.Text = currentGenerator.ParameterDescription(2); + lbParam3Val.Text = currentGenerator.ParameterDescription(2, sliderParam3.Value); } else { @@ -115,7 +115,7 @@ namespace MeshExplorer { if (currentGenerator != null) { - lbParam1Val.Text = currentGenerator.ParameterDescription(1, sliderParam1.Value); + lbParam1Val.Text = currentGenerator.ParameterDescription(0, sliderParam1.Value); } } @@ -123,7 +123,7 @@ namespace MeshExplorer { if (currentGenerator != null) { - lbParam2Val.Text = currentGenerator.ParameterDescription(2, sliderParam2.Value); + lbParam2Val.Text = currentGenerator.ParameterDescription(1, sliderParam2.Value); } } @@ -131,7 +131,7 @@ namespace MeshExplorer { if (currentGenerator != null) { - lbParam3Val.Text = currentGenerator.ParameterDescription(3, sliderParam3.Value); + lbParam3Val.Text = currentGenerator.ParameterDescription(2, sliderParam3.Value); } } diff --git a/Triangle.NET/TestApp/FormMain.cs b/Triangle.NET/TestApp/FormMain.cs index 8846c48..c7b5f12 100644 --- a/Triangle.NET/TestApp/FormMain.cs +++ b/Triangle.NET/TestApp/FormMain.cs @@ -663,6 +663,13 @@ namespace MeshExplorer return; } + if (menuViewVoronoi.Checked) + { + renderManager.ShowVoronoi = false; + menuViewVoronoi.Checked = false; + return; + } + IVoronoi voronoi; if (mesh.IsPolygon) @@ -677,7 +684,7 @@ namespace MeshExplorer renderData.SetVoronoi(voronoi); renderManager.SetData(renderData); - menuViewVoronoi.Checked = !menuViewVoronoi.Checked; + menuViewVoronoi.Checked = true; } private void menuViewLog_Click(object sender, EventArgs e) diff --git a/Triangle.NET/TestApp/Generators/BaseGenerator.cs b/Triangle.NET/TestApp/Generators/BaseGenerator.cs new file mode 100644 index 0000000..83e4d9b --- /dev/null +++ b/Triangle.NET/TestApp/Generators/BaseGenerator.cs @@ -0,0 +1,87 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace MeshExplorer.Generators +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using TriangleNet.Geometry; + + /// + /// TODO: Update summary. + /// + public abstract class BaseGenerator : IGenerator + { + private static int MAX_PARAMS = 3; + + protected string name = "Name"; + protected string description = "Description"; + protected int parameter = 0; + + protected string[] descriptions = new string[MAX_PARAMS]; + protected int[][] ranges = new int[MAX_PARAMS][]; + + public virtual string Name { get { return name; } } + public virtual string Description { get { return description; } } + public virtual int ParameterCount { get { return parameter; } } + + public virtual string ParameterDescription(int paramIndex) + { + if (descriptions[paramIndex] == null) + { + return String.Empty; + } + + return descriptions[paramIndex]; + } + + public virtual string ParameterDescription(int paramIndex, double paramValue) + { + int[] range = ranges[paramIndex]; + + if (range == null) + { + return String.Empty; + } + + int num = GetParamValueInt(paramIndex, paramValue); + return num.ToString(); + } + + public abstract InputGeometry Generate(double param0, double param1, double param2); + + protected int GetParamValueInt(int paramIndex, double paramOffset) + { + int[] range = ranges[paramIndex]; + + if (range == null) + { + return 0; + } + + return (int)((range[1] - range[0]) / 100.0 * paramOffset + range[0]); + } + + protected double GetParamValueDouble(int paramIndex, double paramOffset) + { + int[] range = ranges[paramIndex]; + + if (range == null) + { + return 0; + } + + return ((range[1] - range[0]) / 100.0 * paramOffset + range[0]); + } + + public override string ToString() + { + return this.Name; + } + } +} diff --git a/Triangle.NET/TestApp/Generators/BoxWithHole.cs b/Triangle.NET/TestApp/Generators/BoxWithHole.cs index 6424e9c..efa5a4e 100644 --- a/Triangle.NET/TestApp/Generators/BoxWithHole.cs +++ b/Triangle.NET/TestApp/Generators/BoxWithHole.cs @@ -15,89 +15,32 @@ namespace MeshExplorer.Generators /// /// Generates a star contained in a box. /// - public class BoxWithHole : IGenerator + public class BoxWithHole : BaseGenerator { - public string Name + public BoxWithHole() { - get { return "Box with Hole"; } + name = "Box with Hole"; + description = ""; + parameter = 3; + + descriptions[0] = "Points on box sides:"; + descriptions[1] = "Points on hole:"; + descriptions[2] = "Radius:"; + + ranges[0] = new int[] { 5, 50 }; + ranges[1] = new int[] { 10, 200 }; + ranges[2] = new int[] { 5, 20 }; } - public string Description + public override InputGeometry Generate(double param0, double param1, double param2) { - get { return ""; } - } - - public int ParameterCount - { - get { return 3; } - } - - public string ParameterDescription(int paramIndex) - { - if (paramIndex == 1) - { - return "Points on box sides:"; - } - - if (paramIndex == 2) - { - return "Points on hole:"; - } - - if (paramIndex == 3) - { - return "Radius:"; - } - - return ""; - } - - public string ParameterDescription(int paramIndex, double paramValue) - { - if (paramIndex == 1) - { - int numPoints = (int)((50.0 - 5.0) / 100.0 * paramValue + 5.0); - - if (numPoints < 5) - { - numPoints = 5; - } - - return numPoints.ToString(); - } - - if (paramIndex == 2) - { - int numPoints = (int)((100.0 - 10.0) / 100.0 * paramValue + 10.0); - numPoints = (numPoints / 5) * 5; - - if (numPoints < 10) - { - numPoints = 10; - } - - return numPoints.ToString(); - } - - if (paramIndex == 3) - { - int radius = (int)((20.0 - 5.0) / 100.0 * paramValue + 5.0); - - return radius.ToString(); - } - - return ""; - } - - public InputGeometry Generate(double param1, double param2, double param3) - { - int numPoints = (int)((100.0 - 10.0) / 100.0 * param2 + 10.0); + int numPoints = GetParamValueInt(1, param1); InputGeometry input = new InputGeometry(numPoints + 4); double x, y, step = 2 * Math.PI / numPoints; - double r = (int)((20.0 - 5.0) / 100.0 * param3 + 5.0); + double r = GetParamValueInt(2, param2); // Generate circle for (int i = 0; i < numPoints; i++) @@ -111,7 +54,7 @@ namespace MeshExplorer.Generators numPoints = input.Count; - int numPointsB = (int)((50.0 - 5.0) / 100.0 * param1 + 5.0); + int numPointsB = GetParamValueInt(0, param0); // Box sides are 100 units long step = 100.0 / numPointsB; @@ -154,10 +97,5 @@ namespace MeshExplorer.Generators return input; } - - public override string ToString() - { - return this.Name; - } } } diff --git a/Triangle.NET/TestApp/Generators/CircleWithHole.cs b/Triangle.NET/TestApp/Generators/CircleWithHole.cs index ccac84f..bcd8c80 100644 --- a/Triangle.NET/TestApp/Generators/CircleWithHole.cs +++ b/Triangle.NET/TestApp/Generators/CircleWithHole.cs @@ -15,68 +15,28 @@ namespace MeshExplorer.Generators /// /// Generates a ring polygon. /// - public class CircleWithHole : IGenerator + public class CircleWithHole : BaseGenerator { - // Parameters range - double[] range1 = { 100, 250 }; - double[] range2 = { 2, 15 }; - - public string Name + public CircleWithHole() { - get { return "Circle with Hole"; } + name = "Circle with Hole"; + description = ""; + parameter = 2; + + descriptions[0] = "Number of points:"; + descriptions[1] = "Outer radius:"; + + ranges[0] = new int[] { 100, 250 }; + ranges[1] = new int[] { 2, 15 }; } - public string Description - { - get { return ""; } - } - - public int ParameterCount - { - get { return 2; } - } - - public string ParameterDescription(int paramIndex) - { - if (paramIndex == 1) - { - return "Number of points:"; - } - - if (paramIndex == 2) - { - return "Outer radius:"; - } - - return ""; - } - - public string ParameterDescription(int paramIndex, double paramValue) - { - if (paramIndex == 1) - { - int num = (int)((range1[1] - range1[0]) / 100.0 * paramValue + range1[0]); - - return num.ToString(); - } - - if (paramIndex == 2) - { - int radius = (int)((range2[1] - range2[0]) / 100.0 * paramValue + range2[0]); - - return radius.ToString(); - } - - return ""; - } - - public InputGeometry Generate(double param1, double param2, double param3) + public override InputGeometry Generate(double param0, double param1, double param2) { // Number of points on the outer circle - int n = (int)((range1[1] - range1[0]) / 100.0 * param1 + range1[0]); + int n = GetParamValueInt(0, param0); int count, npoints; - double radius = (int)((range2[1] - range2[0]) / 100.0 * param2 + range2[0]); + double radius = GetParamValueInt(1, param1); // Step size on the outer circle double h = 2 * Math.PI * radius / n; @@ -122,7 +82,7 @@ namespace MeshExplorer.Generators input.AddHole(0, 0); - // Regions: |------|------|---| + // Regions: |++++++|++++++|---| // r 1 0 input.AddRegion((r + 3.0) / 4.0, 0, 1); @@ -130,10 +90,5 @@ namespace MeshExplorer.Generators return input; } - - public override string ToString() - { - return this.Name; - } } } diff --git a/Triangle.NET/TestApp/Generators/RandomPoints.cs b/Triangle.NET/TestApp/Generators/RandomPoints.cs index 15f6f8f..fe7c1c3 100644 --- a/Triangle.NET/TestApp/Generators/RandomPoints.cs +++ b/Triangle.NET/TestApp/Generators/RandomPoints.cs @@ -15,66 +15,26 @@ namespace MeshExplorer.Generators /// /// Simple random points generator. /// - public class RandomPoints : IGenerator + public class RandomPoints : BaseGenerator { - public string Name + public RandomPoints() { - get { return "Random Points"; } + name = "Random Points"; + description = ""; + parameter = 3; + + descriptions[0] = "Number of points:"; + descriptions[1] = "Width:"; + descriptions[2] = "Height:"; + + ranges[0] = new int[] { 5, 5000 }; + ranges[1] = new int[] { 10, 200 }; + ranges[2] = new int[] { 10, 200 }; } - public string Description + public override InputGeometry Generate(double param0, double param1, double param2) { - get { return ""; } - } - - public int ParameterCount - { - get { return 3; } - } - - public string ParameterDescription(int paramIndex) - { - if (paramIndex == 1) - { - return "Number of points:"; - } - - if (paramIndex == 2) - { - return "Width:"; - } - - return "Height:"; - } - - public string ParameterDescription(int paramIndex, double paramValue) - { - if (paramIndex == 1) - { - int numPoints = (int)((5000.0 - 5.0) / 100.0 * paramValue + 5.0); - numPoints = (numPoints / 10) * 10; - - if (numPoints < 5) - { - numPoints = 5; - } - - return numPoints.ToString(); - } - - if (paramIndex == 2 || paramIndex == 3) - { - int numPoints = (int)paramValue + 100; - - return numPoints.ToString(); - } - - return ""; - } - - public InputGeometry Generate(double param1, double param2, double param3) - { - int numPoints = (int)((5000.0 - 5.0) / 100.0 * param1 + 5.0); + int numPoints = GetParamValueInt(0, param0); numPoints = (numPoints / 10) * 10; if (numPoints < 5) @@ -84,8 +44,8 @@ namespace MeshExplorer.Generators InputGeometry input = new InputGeometry(numPoints); - int width = (int)param2 + 100; - int height = (int)param3 + 100; + int width = GetParamValueInt(1, param1); + int height = GetParamValueInt(2, param2); for (int i = 0; i < numPoints; i++) { @@ -95,10 +55,5 @@ namespace MeshExplorer.Generators return input; } - - public override string ToString() - { - return this.Name; - } } } diff --git a/Triangle.NET/TestApp/Generators/RandomPointsCircle.cs b/Triangle.NET/TestApp/Generators/RandomPointsCircle.cs index 40f4a95..788be09 100644 --- a/Triangle.NET/TestApp/Generators/RandomPointsCircle.cs +++ b/Triangle.NET/TestApp/Generators/RandomPointsCircle.cs @@ -15,43 +15,26 @@ namespace MeshExplorer.Generators /// /// Simple random points generator (points distributed in a circle). /// - public class RandomPointsCircle : IGenerator + public class RandomPointsCircle : BaseGenerator { - public string Name + public RandomPointsCircle() { - get { return "Random Points (Circle)"; } + name = "Random Points (Circle)"; + description = ""; + parameter = 2; + + descriptions[0] = "Number of points:"; + descriptions[1] = "Distribution:"; + + ranges[0] = new int[] { 5, 5000 }; + ranges[1] = new int[] { 0, 1 }; } - public string Description + public override string ParameterDescription(int paramIndex, double paramValue) { - get { return ""; } - } - - public int ParameterCount - { - get { return 2; } - } - - public string ParameterDescription(int paramIndex) - { - if (paramIndex == 1) + if (paramIndex == 0) { - return "Number of points:"; - } - - if (paramIndex == 2) - { - return "Distribution:"; - } - - return ""; - } - - public string ParameterDescription(int paramIndex, double paramValue) - { - if (paramIndex == 1) - { - int numPoints = (int)((5000.0 - 5.0) / 100.0 * paramValue + 5.0); + int numPoints = GetParamValueInt(paramIndex, paramValue); numPoints = (numPoints / 10) * 10; if (numPoints < 5) @@ -62,7 +45,7 @@ namespace MeshExplorer.Generators return numPoints.ToString(); } - if (paramIndex == 2) + if (paramIndex == 1) { double exp = (paramValue + 10) / 100; @@ -77,9 +60,9 @@ namespace MeshExplorer.Generators return ""; } - public InputGeometry Generate(double param1, double param2, double param3) + public override InputGeometry Generate(double param0, double param1, double param2) { - int numPoints = (int)((5000.0 - 5.0) / 100.0 * param1 + 5.0); + int numPoints = GetParamValueInt(0, param0); numPoints = (numPoints / 10) * 10; if (numPoints < 5) @@ -87,7 +70,7 @@ namespace MeshExplorer.Generators numPoints = 5; } - double exp = (param2 + 10) / 100; + double exp = (param1 + 10) / 100; InputGeometry input = new InputGeometry(numPoints); @@ -116,10 +99,5 @@ namespace MeshExplorer.Generators return input; } - - public override string ToString() - { - return this.Name; - } } } diff --git a/Triangle.NET/TestApp/Generators/RingPolygon.cs b/Triangle.NET/TestApp/Generators/RingPolygon.cs index 00049a1..b508452 100644 --- a/Triangle.NET/TestApp/Generators/RingPolygon.cs +++ b/Triangle.NET/TestApp/Generators/RingPolygon.cs @@ -15,60 +15,41 @@ namespace MeshExplorer.Generators /// /// Generates a ring polygon. /// - public class RingPolygon : IGenerator + public class RingPolygon : BaseGenerator { - public string Name + public RingPolygon() { - get { return "Ring"; } + name = "Ring"; + description = ""; + parameter = 2; + + descriptions[0] = "Number of points:"; + descriptions[1] = "Variation:"; + + ranges[0] = new int[] { 50, 250 }; + ranges[1] = new int[] { 0, 1 }; } - public string Description + public override string ParameterDescription(int paramIndex, double paramValue) { - get { return ""; } - } - - public int ParameterCount - { - get { return 2; } - } - - public string ParameterDescription(int paramIndex) - { - if (paramIndex == 1) + if (paramIndex == 0) { - return "Number of points:"; - } - - if (paramIndex == 2) - { - return "Variation:"; - } - - return ""; - } - - public string ParameterDescription(int paramIndex, double paramValue) - { - if (paramIndex == 1) - { - int numRays = (int)((250.0 - 50.0) / 100.0 * paramValue + 50.0); - + int numRays = GetParamValueInt(paramIndex, paramValue); return numRays.ToString(); } - if (paramIndex == 2) + if (paramIndex == 1) { - double variation = (paramValue + 1) / 100; - + double variation = GetParamValueDouble(paramIndex, paramValue); return variation.ToString("0.0", Util.Nfi); } return ""; } - public InputGeometry Generate(double param1, double param2, double param3) + public override InputGeometry Generate(double param0, double param1, double param2) { - int n = (int)((250.0 - 50.0) / 100.0 * param1 + 50.0); + int n = GetParamValueInt(0, param0); int m = n / 2; InputGeometry input = new InputGeometry(n + 1); @@ -96,7 +77,7 @@ namespace MeshExplorer.Generators if (i % 2 == 0) { - ro = r + r * Util.Random.NextDouble() * (param2 / 100); + ro = r + r * Util.Random.NextDouble() * (param1 / 100); } input.AddPoint(ro * Math.Cos(i * step + offset), ro * Math.Sin(i * step + offset)); diff --git a/Triangle.NET/TestApp/Generators/StarInBox.cs b/Triangle.NET/TestApp/Generators/StarInBox.cs index ecc40ea..3358072 100644 --- a/Triangle.NET/TestApp/Generators/StarInBox.cs +++ b/Triangle.NET/TestApp/Generators/StarInBox.cs @@ -15,48 +15,22 @@ namespace MeshExplorer.Generators /// /// Generates a star contained in a box. /// - public class StarInBox : IGenerator + public class StarInBox : BaseGenerator { - public string Name + public StarInBox() { - get { return "Star in Box"; } + name = "Star in Box"; + description = ""; + parameter = 1; + + descriptions[0] = "Number of rays:"; + + ranges[0] = new int[] { 3, 61 }; } - public string Description + public override InputGeometry Generate(double param0, double param1, double param2) { - get { return ""; } - } - - public int ParameterCount - { - get { return 1; } - } - - public string ParameterDescription(int paramIndex) - { - if (paramIndex == 1) - { - return "Number of rays:"; - } - - return ""; - } - - public string ParameterDescription(int paramIndex, double paramValue) - { - if (paramIndex == 1) - { - int numRays = (int)((61.0 - 3.0) / 100.0 * paramValue + 3.0); - - return numRays.ToString(); - } - - return ""; - } - - public InputGeometry Generate(double param1, double param2, double param3) - { - int numRays = (int)((61.0 - 3.0) / 100.0 * param1 + 3.0); + int numRays = GetParamValueInt(0, param0); InputGeometry input = new InputGeometry(numRays + 4); @@ -88,10 +62,5 @@ namespace MeshExplorer.Generators return input; } - - public override string ToString() - { - return this.Name; - } } } diff --git a/Triangle.NET/TestApp/Mesh Explorer.csproj b/Triangle.NET/TestApp/Mesh Explorer.csproj index d174071..3788f9e 100644 --- a/Triangle.NET/TestApp/Mesh Explorer.csproj +++ b/Triangle.NET/TestApp/Mesh Explorer.csproj @@ -96,6 +96,7 @@ FormMain.cs + diff --git a/Triangle.NET/Triangle/Algorithm/Dwyer.cs b/Triangle.NET/Triangle/Algorithm/Dwyer.cs index ebf83df..335729d 100644 --- a/Triangle.NET/Triangle/Algorithm/Dwyer.cs +++ b/Triangle.NET/Triangle/Algorithm/Dwyer.cs @@ -756,13 +756,13 @@ namespace TriangleNet.Algorithm divider = vertices >> 1; // Recursively triangulate each half. DivconqRecurse(left, left + divider - 1, 1 - axis, ref farleft, ref innerleft); - ///dbgWriter.Write(); + //DebugWriter.Session.Write(mesh, true); DivconqRecurse(left + divider, right, 1 - axis, ref innerright, ref farright); - ///dbgWriter.Write(); + //DebugWriter.Session.Write(mesh, true); // Merge the two triangulations into one. MergeHulls(ref farleft, ref innerleft, ref innerright, ref farright, axis); - ///dbgWriter.Write(); + //DebugWriter.Session.Write(mesh, true); } } @@ -838,6 +838,8 @@ namespace TriangleNet.Algorithm this.mesh = m; + //DebugWriter.Session.Start("test-dbg"); + // Allocate an array of pointers to vertices for sorting. // TODO: use ToArray this.sortarray = new Vertex[m.invertices]; @@ -888,7 +890,9 @@ namespace TriangleNet.Algorithm // Form the Delaunay triangulation. DivconqRecurse(0, i-1, 0, ref hullleft, ref hullright); - //trifree((VOID*)sortarray); + + //DebugWriter.Session.Write(mesh); + //DebugWriter.Session.Finish(); return RemoveGhosts(ref hullleft); } diff --git a/Triangle.NET/Triangle/Carver.cs b/Triangle.NET/Triangle/Carver.cs index 1e51d3c..12e20f7 100644 --- a/Triangle.NET/Triangle/Carver.cs +++ b/Triangle.NET/Triangle/Carver.cs @@ -332,7 +332,7 @@ namespace TriangleNet if (Primitives.CounterClockwise(searchorg, searchdest, hole) > 0.0) { // Find a triangle that contains the hole. - intersect = mesh.Locate(hole, ref searchtri); + intersect = mesh.locator.Locate(hole, ref searchtri); if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) { // Infect the triangle. This is done by marking the triangle @@ -374,7 +374,7 @@ namespace TriangleNet if (Primitives.CounterClockwise(searchorg, searchdest, region.point) > 0.0) { // Find a triangle that contains the region point. - intersect = mesh.Locate(region.point, ref searchtri); + intersect = mesh.locator.Locate(region.point, ref searchtri); if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected())) { // Record the triangle for processing after the diff --git a/Triangle.NET/Triangle/Geometry/BoundingBox.cs b/Triangle.NET/Triangle/Geometry/BoundingBox.cs index 1f12aa4..b3f30ae 100644 --- a/Triangle.NET/Triangle/Geometry/BoundingBox.cs +++ b/Triangle.NET/Triangle/Geometry/BoundingBox.cs @@ -103,6 +103,19 @@ namespace TriangleNet.Geometry ymax = Math.Max(ymax, y); } + /// + /// Scale bounds. + /// + /// Add dx to left and right bounds. + /// Add dy to top and bottom bounds. + public void Scale(double dx, double dy) + { + xmin -= dx; + xmax += dx; + ymin -= dy; + ymax += dy; + } + /// /// Check if given point is inside bounding box. /// diff --git a/Triangle.NET/Triangle/IO/DataReader.cs b/Triangle.NET/Triangle/IO/DataReader.cs index 78b6901..6ef37be 100644 --- a/Triangle.NET/Triangle/IO/DataReader.cs +++ b/Triangle.NET/Triangle/IO/DataReader.cs @@ -75,6 +75,7 @@ namespace TriangleNet.IO int numberofsegments = input.segments.Count; mesh.inelements = elements; + mesh.regions.AddRange(input.regions); // Create the triangles. for (i = 0; i < mesh.inelements; i++) diff --git a/Triangle.NET/Triangle/IO/DebugWriter.cs b/Triangle.NET/Triangle/IO/DebugWriter.cs index 2948f19..09da57a 100644 --- a/Triangle.NET/Triangle/IO/DebugWriter.cs +++ b/Triangle.NET/Triangle/IO/DebugWriter.cs @@ -7,9 +7,12 @@ namespace TriangleNet.IO { using System; - using System.IO; using System.Globalization; + using System.IO; + using System.IO.Compression; + using System.Text; using TriangleNet.Data; + using TriangleNet.Geometry; /// /// Writes a the current mesh into a text file. @@ -18,147 +21,243 @@ namespace TriangleNet.IO /// File format: /// /// num_nodes - /// nid_1 nx ny mark + /// id_1 nx ny mark /// ... - /// nid_n nx ny mark + /// id_n nx ny mark /// /// num_segs - /// sid_1 p1 p2 mark + /// id_1 p1 p2 mark /// ... - /// sid_n p1 p2 mark + /// id_n p1 p2 mark /// /// num_tris - /// tid_1 p1 p2 p3 n1 n2 n3 + /// id_1 p1 p2 p3 n1 n2 n3 /// ... - /// tid_n p1 p2 p3 n1 n2 n3 + /// id_n p1 p2 p3 n1 n2 n3 /// class DebugWriter { static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat; - Mesh mesh; int iteration; - string name; + string session; + StreamWriter stream; + string tmpFile; + int[] vertices; + int triangles; - public DebugWriter(Mesh mesh) + #region Singleton pattern + + private static readonly DebugWriter instance = new DebugWriter(); + + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static DebugWriter() { } + + private DebugWriter() { } + + public static DebugWriter Session { - this.mesh = mesh; - - this.iteration = 0; - this.name = "debug-{0}.mesh"; + get + { + return instance; + } } + #endregion + /// /// Start a new session with given name. /// /// Name of the session (and output files). - public void NewSession(string name) + public void Start(string session) { this.iteration = 0; - this.name = name + "-{0}.mesh"; + this.session = session; + + if (this.stream != null) + { + throw new Exception("A session is active. Finish before starting a new."); + } + + this.tmpFile = Path.GetTempFileName(); + this.stream = new StreamWriter(tmpFile); } - + /// - /// Write complete mesh to a .mesh file. + /// Write complete mesh to file. /// - public void Write() + public void Write(Mesh mesh, bool skip = false) { + this.WriteMesh(mesh, skip); + + this.triangles = mesh.Triangles.Count; + } + + /// + /// Finish this session. + /// + public void Finish() + { + this.Finish(session + ".mshx"); + } + + private void Finish(string path) + { + if (stream != null) + { + stream.Flush(); + stream.Dispose(); + stream = null; + + string header = "#!N" + this.iteration + Environment.NewLine; + + using (var gzFile = new FileStream(path, FileMode.Create)) + { + using (var gzStream = new GZipStream(gzFile, CompressionMode.Compress, false)) + { + byte[] bytes = Encoding.UTF8.GetBytes(header); + gzStream.Write(bytes, 0, bytes.Length); + + // TODO: read with stream + bytes = File.ReadAllBytes(tmpFile); + gzStream.Write(bytes, 0, bytes.Length); + } + } + + File.Delete(this.tmpFile); + } + } + + private void WriteGeometry(InputGeometry geometry) + { + stream.WriteLine("#!G{0}", this.iteration++); + } + + private void WriteMesh(Mesh mesh, bool skip) + { + // Mesh may have changed, but we choose to skip + if (triangles == mesh.triangles.Count && skip) + { + return; + } + + // Header line + stream.WriteLine("#!M{0}", this.iteration++); + Vertex p1, p2, p3; - string file = String.Format(name, iteration++); - - using (StreamWriter writer = new StreamWriter(file)) + if (VerticesChanged(mesh)) { + HashVertices(mesh); + // Number of vertices. - writer.WriteLine("{0}", mesh.vertices.Count); + stream.WriteLine("{0}", mesh.vertices.Count); foreach (var v in mesh.vertices.Values) { // Vertex number, x and y coordinates and marker. - writer.WriteLine("{0} {1} {2} {3}", v.hash, v.x.ToString(nfi), v.y.ToString(nfi), v.mark); + stream.WriteLine("{0} {1} {2} {3}", v.hash, v.x.ToString(nfi), v.y.ToString(nfi), v.mark); } + } + else + { + stream.WriteLine("0"); + } - // Number of segments. - writer.WriteLine("{0}", mesh.subsegs.Count); + // Number of segments. + stream.WriteLine("{0}", mesh.subsegs.Count); - Osub subseg = default(Osub); - subseg.orient = 0; + Osub subseg = default(Osub); + subseg.orient = 0; - foreach (var item in mesh.subsegs.Values) + foreach (var item in mesh.subsegs.Values) + { + if (item.hash <= 0) { - if (item.hash <= 0) - { - continue; - } - - subseg.seg = item; - - p1 = subseg.Org(); - p2 = subseg.Dest(); - - // Segment number, indices of its two endpoints, and marker. - writer.WriteLine("{0} {1} {2} {3}", subseg.seg.hash, p1.hash, p2.hash, subseg.seg.boundary); + continue; } - Otri tri = default(Otri), trisym = default(Otri); + subseg.seg = item; + + p1 = subseg.Org(); + p2 = subseg.Dest(); + + // Segment number, indices of its two endpoints, and marker. + stream.WriteLine("{0} {1} {2} {3}", subseg.seg.hash, p1.hash, p2.hash, subseg.seg.boundary); + } + + Otri tri = default(Otri), trisym = default(Otri); + tri.orient = 0; + + int n1, n2, n3, h1, h2, h3; + + // Number of triangles. + stream.WriteLine("{0}", mesh.triangles.Count); + + foreach (var item in mesh.triangles.Values) + { + tri.triangle = item; + + p1 = tri.Org(); + p2 = tri.Dest(); + p3 = tri.Apex(); + + h1 = (p1 == null) ? -1 : p1.hash; + h2 = (p2 == null) ? -1 : p2.hash; + h3 = (p3 == null) ? -1 : p3.hash; + + // Triangle number, indices for three vertices. + stream.Write("{0} {1} {2} {3}", tri.triangle.hash, h1, h2, h3); + + tri.orient = 1; + tri.Sym(ref trisym); + n1 = trisym.triangle.hash; + + tri.orient = 2; + tri.Sym(ref trisym); + n2 = trisym.triangle.hash; + tri.orient = 0; + tri.Sym(ref trisym); + n3 = trisym.triangle.hash; - int n1, n2, n3, hash3; + // Neighboring triangle numbers. + stream.WriteLine(" {0} {1} {2}", n1, n2, n3); + } + } - // Number of triangles. - writer.WriteLine("{0}", mesh.triangles.Count); + private bool VerticesChanged(Mesh mesh) + { + if (vertices == null || mesh.Vertices.Count != vertices.Length) + { + return true; + } - foreach (var item in mesh.triangles.Values) + int i = 0; + foreach (var v in mesh.Vertices) + { + if (v.id != vertices[i++]) { - if (item.hash <= 0) - { - continue; - } - - tri.triangle = item; - - p1 = tri.Org(); - p2 = tri.Dest(); - p3 = tri.Apex(); - - if (p3 == null) - { - if (p1 == null || p2 == null) - { - continue; - } - - hash3 = -1; - } - else - { - hash3 = p3.hash; - } - - if (p1 == null || p2 == null) - { - continue; - } - - // Triangle number, indices for three vertices. - writer.Write("{0} {1} {2} {3}", tri.triangle.hash, p1.hash, p2.hash, hash3); - - tri.orient = 1; - tri.Sym(ref trisym); - n1 = trisym.triangle.hash; - - tri.orient = 2; - tri.Sym(ref trisym); - n2 = trisym.triangle.hash; - - tri.orient = 0; - tri.Sym(ref trisym); - n3 = trisym.triangle.hash; - - // Neighboring triangle numbers. - writer.WriteLine(" {0} {1} {2}", n1, n2, n3); + return true; } } + + return false; + } + + private void HashVertices(Mesh mesh) + { + if (vertices == null || mesh.Vertices.Count != vertices.Length) + { + vertices = new int[mesh.Vertices.Count]; + } + + int i = 0; + foreach (var v in mesh.Vertices) + { + vertices[i++] = v.id; + } } } } diff --git a/Triangle.NET/Triangle/Mesh.cs b/Triangle.NET/Triangle/Mesh.cs index 5b8e00a..aaa353f 100644 --- a/Triangle.NET/Triangle/Mesh.cs +++ b/Triangle.NET/Triangle/Mesh.cs @@ -28,7 +28,6 @@ namespace TriangleNet ILog logger; Quality quality; - Sampler sampler; // Stack that maintains a list of recently flipped triangles. Stack flipstack; @@ -73,9 +72,7 @@ namespace TriangleNet // that isn't really connected to a subsegment at that location. internal static Segment dummysub; - // Pointer to a recently visited triangle. Improves point location if - // proximate vertices are inserted sequentially. - internal Otri recenttri; + internal TriangleLocator locator; // Controls the behavior of the mesh instance. internal Behavior behavior; @@ -174,7 +171,7 @@ namespace TriangleNet } #endregion - + /// /// Initializes a new instance of the class. /// @@ -205,7 +202,7 @@ namespace TriangleNet quality = new Quality(this); - sampler = new Sampler(); + locator = new TriangleLocator(this); Primitives.ExactInit(); @@ -614,7 +611,7 @@ namespace TriangleNet Reset(); - sampler.Reset(); + locator.Reset(); } /// @@ -624,7 +621,6 @@ namespace TriangleNet { numbering = NodeNumbering.None; - recenttri.triangle = null; // No triangle has been visited yet. undeads = 0; // No eliminated input vertices yet. checksegments = false; // There are no segments in the triangulation yet. checkquality = false; // The quality triangulation stage has not begun. @@ -890,13 +886,13 @@ namespace TriangleNet horiz.orient = 0; horiz.SymSelf(); // Search for a triangle containing 'newvertex'. - intersect = Locate(newvertex, ref horiz); + intersect = locator.Locate(newvertex, ref horiz); } else { // Start searching from the triangle provided by the caller. searchtri.Copy(ref horiz); - intersect = PreciseLocate(newvertex, ref horiz, true); + intersect = locator.PreciseLocate(newvertex, ref horiz, true); } } else @@ -912,7 +908,7 @@ namespace TriangleNet // There's already a vertex there. Return in 'searchtri' a triangle // whose origin is the existing vertex. horiz.Copy(ref searchtri); - horiz.Copy(ref recenttri); + locator.Update(ref horiz); return InsertVertexResult.Duplicate; } if ((intersect == LocateResult.OnEdge) || (intersect == LocateResult.Outside)) @@ -949,7 +945,7 @@ namespace TriangleNet // Return a handle whose primary edge contains the vertex, // which has not been inserted. horiz.Copy(ref searchtri); - horiz.Copy(ref recenttri); + locator.Update(ref horiz); return InsertVertexResult.Violating; } } @@ -1360,7 +1356,11 @@ namespace TriangleNet { // We're done. Return a triangle whose origin is the new vertex. horiz.Lnext(ref searchtri); + + Otri recenttri = default(Otri); horiz.Lnext(ref recenttri); + locator.Update(ref recenttri); + return success; } // Finish finding the next edge around the newly inserted vertex. @@ -1982,307 +1982,6 @@ namespace TriangleNet #endregion - #region Location - - /// - /// Find a triangle or edge containing a given point. - /// - /// The point to locate. - /// The triangle to start the search at. - /// If 'stopatsubsegment' is set, the search - /// will stop if it tries to walk through a subsegment, and will return OUTSIDE. - /// Location information. - /// - /// Begins its search from 'searchtri'. It is important that 'searchtri' - /// be a handle with the property that 'searchpoint' is strictly to the left - /// of the edge denoted by 'searchtri', or is collinear with that edge and - /// does not intersect that edge. (In particular, 'searchpoint' should not - /// be the origin or destination of that edge.) - /// - /// These conditions are imposed because preciselocate() is normally used in - /// one of two situations: - /// - /// (1) To try to find the location to insert a new point. Normally, we - /// know an edge that the point is strictly to the left of. In the - /// incremental Delaunay algorithm, that edge is a bounding box edge. - /// In Ruppert's Delaunay refinement algorithm for quality meshing, - /// that edge is the shortest edge of the triangle whose circumcenter - /// is being inserted. - /// - /// (2) To try to find an existing point. In this case, any edge on the - /// convex hull is a good starting edge. You must screen out the - /// possibility that the vertex sought is an endpoint of the starting - /// edge before you call preciselocate(). - /// - /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. - /// - /// This implementation differs from that given by Guibas and Stolfi. It - /// walks from triangle to triangle, crossing an edge only if 'searchpoint' - /// is on the other side of the line containing that edge. After entering - /// a triangle, there are two edges by which one can leave that triangle. - /// If both edges are valid ('searchpoint' is on the other side of both - /// edges), one of the two is chosen by drawing a line perpendicular to - /// the entry edge (whose endpoints are 'forg' and 'fdest') passing through - /// 'fapex'. Depending on which side of this perpendicular 'searchpoint' - /// falls on, an exit edge is chosen. - /// - /// This implementation is empirically faster than the Guibas and Stolfi - /// point location routine (which I originally used), which tends to spiral - /// in toward its target. - /// - /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' - /// is a handle whose origin is the existing vertex. - /// - /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a - /// handle whose primary edge is the edge on which the point lies. - /// - /// Returns INTRIANGLE if the point lies strictly within a triangle. - /// 'searchtri' is a handle on the triangle that contains the point. - /// - /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a - /// handle whose primary edge the point is to the right of. This might - /// occur when the circumcenter of a triangle falls just slightly outside - /// the mesh due to floating-point roundoff error. It also occurs when - /// seeking a hole or region point that a foolish user has placed outside - /// the mesh. - /// - /// WARNING: This routine is designed for convex triangulations, and will - /// not generally work after the holes and concavities have been carved. - /// However, it can still be used to find the circumcenter of a triangle, as - /// long as the search is begun from the triangle in question. - internal LocateResult PreciseLocate(Point searchpoint, ref Otri searchtri, - bool stopatsubsegment) - { - Otri backtracktri = default(Otri); - Osub checkedge = default(Osub); - Vertex forg, fdest, fapex; - double orgorient, destorient; - bool moveleft; - - // Where are we? - forg = searchtri.Org(); - fdest = searchtri.Dest(); - fapex = searchtri.Apex(); - while (true) - { - // Check whether the apex is the point we seek. - if ((fapex.x == searchpoint.X) && (fapex.y == searchpoint.Y)) - { - searchtri.LprevSelf(); - return LocateResult.OnVertex; - } - // Does the point lie on the other side of the line defined by the - // triangle edge opposite the triangle's destination? - destorient = Primitives.CounterClockwise(forg, fapex, searchpoint); - // Does the point lie on the other side of the line defined by the - // triangle edge opposite the triangle's origin? - orgorient = Primitives.CounterClockwise(fapex, fdest, searchpoint); - if (destorient > 0.0) - { - if (orgorient > 0.0) - { - // Move left if the inner product of (fapex - searchpoint) and - // (fdest - forg) is positive. This is equivalent to drawing - // a line perpendicular to the line (forg, fdest) and passing - // through 'fapex', and determining which side of this line - // 'searchpoint' falls on. - moveleft = (fapex.x - searchpoint.X) * (fdest.x - forg.x) + - (fapex.y - searchpoint.Y) * (fdest.y - forg.y) > 0.0; - } - else - { - moveleft = true; - } - } - else - { - if (orgorient > 0.0) - { - moveleft = false; - } - else - { - // The point we seek must be on the boundary of or inside this - // triangle. - if (destorient == 0.0) - { - searchtri.LprevSelf(); - return LocateResult.OnEdge; - } - if (orgorient == 0.0) - { - searchtri.LnextSelf(); - return LocateResult.OnEdge; - } - return LocateResult.InTriangle; - } - } - - // Move to another triangle. Leave a trace 'backtracktri' in case - // floating-point roundoff or some such bogey causes us to walk - // off a boundary of the triangulation. - if (moveleft) - { - searchtri.Lprev(ref backtracktri); - fdest = fapex; - } - else - { - searchtri.Lnext(ref backtracktri); - forg = fapex; - } - backtracktri.Sym(ref searchtri); - - if (checksegments && stopatsubsegment) - { - // Check for walking through a subsegment. - backtracktri.SegPivot(ref checkedge); - if (checkedge.seg != dummysub) - { - // Go back to the last triangle. - backtracktri.Copy(ref searchtri); - return LocateResult.Outside; - } - } - // Check for walking right out of the triangulation. - if (searchtri.triangle == dummytri) - { - // Go back to the last triangle. - backtracktri.Copy(ref searchtri); - return LocateResult.Outside; - } - - fapex = searchtri.Apex(); - } - } - - /// - /// Find a triangle or edge containing a given point. - /// - /// The point to locate. - /// The triangle to start the search at. - /// Location information. - /// - /// Searching begins from one of: the input 'searchtri', a recently - /// encountered triangle 'recenttri', or from a triangle chosen from a - /// random sample. The choice is made by determining which triangle's - /// origin is closest to the point we are searching for. Normally, - /// 'searchtri' should be a handle on the convex hull of the triangulation. - /// - /// Details on the random sampling method can be found in the Mucke, Saias, - /// and Zhu paper cited in the header of this code. - /// - /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. - /// - /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' - /// is a handle whose origin is the existing vertex. - /// - /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a - /// handle whose primary edge is the edge on which the point lies. - /// - /// Returns INTRIANGLE if the point lies strictly within a triangle. - /// 'searchtri' is a handle on the triangle that contains the point. - /// - /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a - /// handle whose primary edge the point is to the right of. This might - /// occur when the circumcenter of a triangle falls just slightly outside - /// the mesh due to floating-point roundoff error. It also occurs when - /// seeking a hole or region point that a foolish user has placed outside - /// the mesh. - /// - /// WARNING: This routine is designed for convex triangulations, and will - /// not generally work after the holes and concavities have been carved. - /// - internal LocateResult Locate(Point searchpoint, ref Otri searchtri) - { - Otri sampletri = default(Otri); - Vertex torg, tdest; - double searchdist, dist; - double ahead; - - // Record the distance from the suggested starting triangle to the - // point we seek. - torg = searchtri.Org(); - searchdist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + - (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); - - // If a recently encountered triangle has been recorded and has not been - // deallocated, test it as a good starting point. - if (recenttri.triangle != null) - { - if (!Otri.IsDead(recenttri.triangle)) - { - torg = recenttri.Org(); - if ((torg.x == searchpoint.X) && (torg.y == searchpoint.Y)) - { - recenttri.Copy(ref searchtri); - return LocateResult.OnVertex; - } - dist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + - (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); - if (dist < searchdist) - { - recenttri.Copy(ref searchtri); - searchdist = dist; - } - } - } - - // TODO: Improve sampling. - sampler.Update(this); - int[] samples = sampler.GetSamples(this); - - foreach (var key in samples) - { - sampletri.triangle = this.triangles[key]; - if (!Otri.IsDead(sampletri.triangle)) - { - torg = sampletri.Org(); - dist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + - (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); - if (dist < searchdist) - { - sampletri.Copy(ref searchtri); - searchdist = dist; - } - } - } - - // Where are we? - torg = searchtri.Org(); - tdest = searchtri.Dest(); - // Check the starting triangle's vertices. - if ((torg.x == searchpoint.X) && (torg.y == searchpoint.Y)) - { - return LocateResult.OnVertex; - } - if ((tdest.x == searchpoint.X) && (tdest.y == searchpoint.Y)) - { - searchtri.LnextSelf(); - return LocateResult.OnVertex; - } - // Orient 'searchtri' to fit the preconditions of calling preciselocate(). - ahead = Primitives.CounterClockwise(torg, tdest, searchpoint); - if (ahead < 0.0) - { - // Turn around so that 'searchpoint' is to the left of the - // edge specified by 'searchtri'. - searchtri.SymSelf(); - } - else if (ahead == 0.0) - { - // Check if 'searchpoint' is between 'torg' and 'tdest'. - if (((torg.x < searchpoint.X) == (searchpoint.X < tdest.x)) && - ((torg.y < searchpoint.Y) == (searchpoint.Y < tdest.y))) - { - return LocateResult.OnEdge; - } - } - return PreciseLocate(searchpoint, ref searchtri, false); - } - - #endregion - #region Segment insertion /// @@ -2850,14 +2549,14 @@ namespace TriangleNet searchtri1.orient = 0; searchtri1.SymSelf(); // Search for the segment's first endpoint by point location. - if (Locate(endpoint1, ref searchtri1) != LocateResult.OnVertex) + if (locator.Locate(endpoint1, ref searchtri1) != LocateResult.OnVertex) { logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().1"); throw new Exception("Unable to locate PSLG vertex in triangulation."); } } // Remember this triangle to improve subsequent point location. - searchtri1.Copy(ref recenttri); + locator.Update(ref searchtri1); // Scout the beginnings of a path from the first endpoint // toward the second. @@ -2884,14 +2583,14 @@ namespace TriangleNet searchtri2.orient = 0; searchtri2.SymSelf(); // Search for the segment's second endpoint by point location. - if (Locate(endpoint2, ref searchtri2) != LocateResult.OnVertex) + if (locator.Locate(endpoint2, ref searchtri2) != LocateResult.OnVertex) { logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().2"); throw new Exception("Unable to locate PSLG vertex in triangulation."); } } // Remember this triangle to improve subsequent point location. - searchtri2.Copy(ref recenttri); + locator.Update(ref searchtri2); // Scout the beginnings of a path from the second endpoint // toward the first. if (ScoutSegment(ref searchtri2, endpoint1, newmark)) diff --git a/Triangle.NET/Triangle/NewLocation.cs b/Triangle.NET/Triangle/NewLocation.cs index 4018ba0..25ccd86 100644 --- a/Triangle.NET/Triangle/NewLocation.cs +++ b/Triangle.NET/Triangle/NewLocation.cs @@ -4079,7 +4079,7 @@ namespace TriangleNet // edge specified by 'searchtri'. searchtri.SymSelf(); searchtri.Copy(ref horiz); - intersect = mesh.PreciseLocate(newvertex, ref horiz, false); + intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); } else if (ahead == 0.0) { @@ -4095,7 +4095,7 @@ namespace TriangleNet else { searchtri.Copy(ref horiz); - intersect = mesh.PreciseLocate(newvertex, ref horiz, false); + intersect = mesh.locator.PreciseLocate(newvertex, ref horiz, false); } } if (intersect == LocateResult.OnVertex || intersect == LocateResult.Outside) diff --git a/Triangle.NET/Triangle/Quality.cs b/Triangle.NET/Triangle/Quality.cs index 5884f43..bc41819 100644 --- a/Triangle.NET/Triangle/Quality.cs +++ b/Triangle.NET/Triangle/Quality.cs @@ -123,6 +123,17 @@ namespace TriangleNet } } + // Check for unconnected vertices + mesh.MakeVertexMap(); + foreach (var v in mesh.vertices.Values) + { + if (v.tri.triangle == null) + { + logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)", + "Quality.CheckMesh()"); + } + } + if (horrors == 0) // && Behavior.Verbose { logger.Info("Mesh topology appears to be consistent."); diff --git a/Triangle.NET/Triangle/Tools/BoundedVoronoi.cs b/Triangle.NET/Triangle/Tools/BoundedVoronoi.cs index 5b357e0..91c0156 100644 --- a/Triangle.NET/Triangle/Tools/BoundedVoronoi.cs +++ b/Triangle.NET/Triangle/Tools/BoundedVoronoi.cs @@ -413,13 +413,17 @@ namespace TriangleNet.Tools f.Onext(ref f_next); } - // Add vertex on border - p = new Point(vertex.x, vertex.y); - p.id = n + segIndex; - points[n + segIndex] = p; - segIndex++; + if (f_prev.triangle == Mesh.dummytri) + { + // For vertices on the domain boundaray, add the vertex. For + // internal boundaries don't add it. + p = new Point(vertex.x, vertex.y); + p.id = n + segIndex; + points[n + segIndex] = p; + segIndex++; - vpoints.Add(p); + vpoints.Add(p); + } // Add midpoint of start triangles' edge. torg = f.Org(); diff --git a/Triangle.NET/Triangle/Tools/Voronoi.cs b/Triangle.NET/Triangle/Tools/Voronoi.cs index c1e6919..6f0679f 100644 --- a/Triangle.NET/Triangle/Tools/Voronoi.cs +++ b/Triangle.NET/Triangle/Tools/Voronoi.cs @@ -5,9 +5,10 @@ // // ----------------------------------------------------------------------- +using System; +using System.Collections.Generic; using TriangleNet.Data; using TriangleNet.Geometry; -using System.Collections.Generic; namespace TriangleNet.Tools { @@ -21,9 +22,13 @@ namespace TriangleNet.Tools Point[] points; List regions; + // Stores the endpoints of rays of infinite Voronoi cells Dictionary rayPoints; int rayIndex; + // Bounding box of the triangles circumcenters. + BoundingBox bounds; + /// /// Initializes a new instance of the class. /// @@ -74,11 +79,14 @@ namespace TriangleNet.Tools this.points = new Point[mesh.triangles.Count + mesh.hullsize]; this.regions = new List(mesh.vertices.Count); - ComputeCircumCenters(); - rayPoints = new Dictionary(); rayIndex = 0; + bounds = new BoundingBox(); + + // Compute triangles circumcenters and setup bounding box + ComputeCircumCenters(); + // Loop over the mesh vertices (Voronoi generators). foreach (var item in mesh.vertices.Values) { @@ -104,11 +112,16 @@ namespace TriangleNet.Tools pt.id = item.id; points[item.id] = pt; + + bounds.Update(pt.x, pt.y); } + + double ds = Math.Max(bounds.Width, bounds.Height); + bounds.Scale(ds, ds); } /// - /// + /// Construct Voronoi region for given vertex. /// /// /// The circumcenter indices which make up the cell. @@ -132,6 +145,7 @@ namespace TriangleNet.Tools f_init.Copy(ref f); f_init.Onext(ref f_next); + // Check if f_init lies on the boundary of the triangulation. if (f_next.triangle == Mesh.dummytri) { f_init.Oprev(ref f_prev); @@ -216,9 +230,6 @@ namespace TriangleNet.Tools f.SegPivot(ref sub); sid = sub.seg.hash; - // Last valid f lies at the boundary. Add the circumcenter. - vpoints.Add(points[f.triangle.id]); - if (rayPoints.ContainsKey(sid)) { vpoints.Add(rayPoints[sid]); @@ -254,16 +265,11 @@ namespace TriangleNet.Tools double t1, x1, y1, t2, x2, y2; - // Bounding box (50% enlarged) - var box = mesh.Bounds; - - double dw = box.Width * 0.5f; - double dh = box.Height * 0.5f; - - double minX = box.Xmin - dw; - double maxX = box.Xmax + dw; - double minY = box.Ymin - dh; - double maxY = box.Ymax + dh; + // Bounding box + double minX = bounds.Xmin; + double maxX = bounds.Xmax; + double minY = bounds.Ymin; + double maxY = bounds.Ymax; // Check if point is inside the bounds if (x < minX || x > maxX || y < minY || y > maxY) diff --git a/Triangle.NET/Triangle/Triangle.csproj b/Triangle.NET/Triangle/Triangle.csproj index 0031228..8bbca83 100644 --- a/Triangle.NET/Triangle/Triangle.csproj +++ b/Triangle.NET/Triangle/Triangle.csproj @@ -93,6 +93,7 @@ + diff --git a/Triangle.NET/Triangle/TriangleLocator.cs b/Triangle.NET/Triangle/TriangleLocator.cs new file mode 100644 index 0000000..1409195 --- /dev/null +++ b/Triangle.NET/Triangle/TriangleLocator.cs @@ -0,0 +1,339 @@ +// ----------------------------------------------------------------------- +// +// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html +// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet +{ + using TriangleNet.Data; + using TriangleNet.Geometry; + + /// + /// TODO: Update summary. + /// + class TriangleLocator + { + Sampler sampler; + Mesh mesh; + + // Pointer to a recently visited triangle. Improves point location if + // proximate vertices are inserted sequentially. + internal Otri recenttri; + + public TriangleLocator(Mesh mesh) + { + this.mesh = mesh; + + sampler = new Sampler(); + } + + public void Update(ref Otri otri) + { + otri.Copy(ref recenttri); + } + + public void Reset() + { + recenttri.triangle = null; // No triangle has been visited yet. + } + + /// + /// Find a triangle or edge containing a given point. + /// + /// The point to locate. + /// The triangle to start the search at. + /// If 'stopatsubsegment' is set, the search + /// will stop if it tries to walk through a subsegment, and will return OUTSIDE. + /// Location information. + /// + /// Begins its search from 'searchtri'. It is important that 'searchtri' + /// be a handle with the property that 'searchpoint' is strictly to the left + /// of the edge denoted by 'searchtri', or is collinear with that edge and + /// does not intersect that edge. (In particular, 'searchpoint' should not + /// be the origin or destination of that edge.) + /// + /// These conditions are imposed because preciselocate() is normally used in + /// one of two situations: + /// + /// (1) To try to find the location to insert a new point. Normally, we + /// know an edge that the point is strictly to the left of. In the + /// incremental Delaunay algorithm, that edge is a bounding box edge. + /// In Ruppert's Delaunay refinement algorithm for quality meshing, + /// that edge is the shortest edge of the triangle whose circumcenter + /// is being inserted. + /// + /// (2) To try to find an existing point. In this case, any edge on the + /// convex hull is a good starting edge. You must screen out the + /// possibility that the vertex sought is an endpoint of the starting + /// edge before you call preciselocate(). + /// + /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. + /// + /// This implementation differs from that given by Guibas and Stolfi. It + /// walks from triangle to triangle, crossing an edge only if 'searchpoint' + /// is on the other side of the line containing that edge. After entering + /// a triangle, there are two edges by which one can leave that triangle. + /// If both edges are valid ('searchpoint' is on the other side of both + /// edges), one of the two is chosen by drawing a line perpendicular to + /// the entry edge (whose endpoints are 'forg' and 'fdest') passing through + /// 'fapex'. Depending on which side of this perpendicular 'searchpoint' + /// falls on, an exit edge is chosen. + /// + /// This implementation is empirically faster than the Guibas and Stolfi + /// point location routine (which I originally used), which tends to spiral + /// in toward its target. + /// + /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' + /// is a handle whose origin is the existing vertex. + /// + /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a + /// handle whose primary edge is the edge on which the point lies. + /// + /// Returns INTRIANGLE if the point lies strictly within a triangle. + /// 'searchtri' is a handle on the triangle that contains the point. + /// + /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a + /// handle whose primary edge the point is to the right of. This might + /// occur when the circumcenter of a triangle falls just slightly outside + /// the mesh due to floating-point roundoff error. It also occurs when + /// seeking a hole or region point that a foolish user has placed outside + /// the mesh. + /// + /// WARNING: This routine is designed for convex triangulations, and will + /// not generally work after the holes and concavities have been carved. + /// However, it can still be used to find the circumcenter of a triangle, as + /// long as the search is begun from the triangle in question. + public LocateResult PreciseLocate(Point searchpoint, ref Otri searchtri, + bool stopatsubsegment) + { + Otri backtracktri = default(Otri); + Osub checkedge = default(Osub); + Vertex forg, fdest, fapex; + double orgorient, destorient; + bool moveleft; + + // Where are we? + forg = searchtri.Org(); + fdest = searchtri.Dest(); + fapex = searchtri.Apex(); + while (true) + { + // Check whether the apex is the point we seek. + if ((fapex.x == searchpoint.X) && (fapex.y == searchpoint.Y)) + { + searchtri.LprevSelf(); + return LocateResult.OnVertex; + } + // Does the point lie on the other side of the line defined by the + // triangle edge opposite the triangle's destination? + destorient = Primitives.CounterClockwise(forg, fapex, searchpoint); + // Does the point lie on the other side of the line defined by the + // triangle edge opposite the triangle's origin? + orgorient = Primitives.CounterClockwise(fapex, fdest, searchpoint); + if (destorient > 0.0) + { + if (orgorient > 0.0) + { + // Move left if the inner product of (fapex - searchpoint) and + // (fdest - forg) is positive. This is equivalent to drawing + // a line perpendicular to the line (forg, fdest) and passing + // through 'fapex', and determining which side of this line + // 'searchpoint' falls on. + moveleft = (fapex.x - searchpoint.X) * (fdest.x - forg.x) + + (fapex.y - searchpoint.Y) * (fdest.y - forg.y) > 0.0; + } + else + { + moveleft = true; + } + } + else + { + if (orgorient > 0.0) + { + moveleft = false; + } + else + { + // The point we seek must be on the boundary of or inside this + // triangle. + if (destorient == 0.0) + { + searchtri.LprevSelf(); + return LocateResult.OnEdge; + } + if (orgorient == 0.0) + { + searchtri.LnextSelf(); + return LocateResult.OnEdge; + } + return LocateResult.InTriangle; + } + } + + // Move to another triangle. Leave a trace 'backtracktri' in case + // floating-point roundoff or some such bogey causes us to walk + // off a boundary of the triangulation. + if (moveleft) + { + searchtri.Lprev(ref backtracktri); + fdest = fapex; + } + else + { + searchtri.Lnext(ref backtracktri); + forg = fapex; + } + backtracktri.Sym(ref searchtri); + + if (mesh.checksegments && stopatsubsegment) + { + // Check for walking through a subsegment. + backtracktri.SegPivot(ref checkedge); + if (checkedge.seg != Mesh.dummysub) + { + // Go back to the last triangle. + backtracktri.Copy(ref searchtri); + return LocateResult.Outside; + } + } + // Check for walking right out of the triangulation. + if (searchtri.triangle == Mesh.dummytri) + { + // Go back to the last triangle. + backtracktri.Copy(ref searchtri); + return LocateResult.Outside; + } + + fapex = searchtri.Apex(); + } + } + + /// + /// Find a triangle or edge containing a given point. + /// + /// The point to locate. + /// The triangle to start the search at. + /// Location information. + /// + /// Searching begins from one of: the input 'searchtri', a recently + /// encountered triangle 'recenttri', or from a triangle chosen from a + /// random sample. The choice is made by determining which triangle's + /// origin is closest to the point we are searching for. Normally, + /// 'searchtri' should be a handle on the convex hull of the triangulation. + /// + /// Details on the random sampling method can be found in the Mucke, Saias, + /// and Zhu paper cited in the header of this code. + /// + /// On completion, 'searchtri' is a triangle that contains 'searchpoint'. + /// + /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri' + /// is a handle whose origin is the existing vertex. + /// + /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a + /// handle whose primary edge is the edge on which the point lies. + /// + /// Returns INTRIANGLE if the point lies strictly within a triangle. + /// 'searchtri' is a handle on the triangle that contains the point. + /// + /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a + /// handle whose primary edge the point is to the right of. This might + /// occur when the circumcenter of a triangle falls just slightly outside + /// the mesh due to floating-point roundoff error. It also occurs when + /// seeking a hole or region point that a foolish user has placed outside + /// the mesh. + /// + /// WARNING: This routine is designed for convex triangulations, and will + /// not generally work after the holes and concavities have been carved. + /// + public LocateResult Locate(Point searchpoint, ref Otri searchtri) + { + Otri sampletri = default(Otri); + Vertex torg, tdest; + double searchdist, dist; + double ahead; + + // Record the distance from the suggested starting triangle to the + // point we seek. + torg = searchtri.Org(); + searchdist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + + (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); + + // If a recently encountered triangle has been recorded and has not been + // deallocated, test it as a good starting point. + if (recenttri.triangle != null) + { + if (!Otri.IsDead(recenttri.triangle)) + { + torg = recenttri.Org(); + if ((torg.x == searchpoint.X) && (torg.y == searchpoint.Y)) + { + recenttri.Copy(ref searchtri); + return LocateResult.OnVertex; + } + dist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + + (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); + if (dist < searchdist) + { + recenttri.Copy(ref searchtri); + searchdist = dist; + } + } + } + + // TODO: Improve sampling. + sampler.Update(mesh); + int[] samples = sampler.GetSamples(mesh); + + foreach (var key in samples) + { + sampletri.triangle = mesh.triangles[key]; + if (!Otri.IsDead(sampletri.triangle)) + { + torg = sampletri.Org(); + dist = (searchpoint.X - torg.x) * (searchpoint.X - torg.x) + + (searchpoint.Y - torg.y) * (searchpoint.Y - torg.y); + if (dist < searchdist) + { + sampletri.Copy(ref searchtri); + searchdist = dist; + } + } + } + + // Where are we? + torg = searchtri.Org(); + tdest = searchtri.Dest(); + // Check the starting triangle's vertices. + if ((torg.x == searchpoint.X) && (torg.y == searchpoint.Y)) + { + return LocateResult.OnVertex; + } + if ((tdest.x == searchpoint.X) && (tdest.y == searchpoint.Y)) + { + searchtri.LnextSelf(); + return LocateResult.OnVertex; + } + // Orient 'searchtri' to fit the preconditions of calling preciselocate(). + ahead = Primitives.CounterClockwise(torg, tdest, searchpoint); + if (ahead < 0.0) + { + // Turn around so that 'searchpoint' is to the left of the + // edge specified by 'searchtri'. + searchtri.SymSelf(); + } + else if (ahead == 0.0) + { + // Check if 'searchpoint' is between 'torg' and 'tdest'. + if (((torg.x < searchpoint.X) == (searchpoint.X < tdest.x)) && + ((torg.y < searchpoint.Y) == (searchpoint.Y < tdest.y))) + { + return LocateResult.OnEdge; + } + } + return PreciseLocate(searchpoint, ref searchtri, false); + } + } +}