Some fixes for Voronoi code
git-svn-id: https://triangle.svn.codeplex.com/svn@73114 0e2699bc-83d4-4a8f-98e7-55e24ab8c7a5
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="BaseGenerator.cs" company="">
|
||||
// TODO: Update copyright text.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace MeshExplorer.Generators
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Update summary.
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,89 +15,32 @@ namespace MeshExplorer.Generators
|
||||
/// <summary>
|
||||
/// Generates a star contained in a box.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,68 +15,28 @@ namespace MeshExplorer.Generators
|
||||
/// <summary>
|
||||
/// Generates a ring polygon.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,66 +15,26 @@ namespace MeshExplorer.Generators
|
||||
/// <summary>
|
||||
/// Simple random points generator.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,43 +15,26 @@ namespace MeshExplorer.Generators
|
||||
/// <summary>
|
||||
/// Simple random points generator (points distributed in a circle).
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,60 +15,41 @@ namespace MeshExplorer.Generators
|
||||
/// <summary>
|
||||
/// Generates a ring polygon.
|
||||
/// </summary>
|
||||
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));
|
||||
|
||||
@@ -15,48 +15,22 @@ namespace MeshExplorer.Generators
|
||||
/// <summary>
|
||||
/// Generates a star contained in a box.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
<Compile Include="FormMain.Designer.cs">
|
||||
<DependentUpon>FormMain.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Generators\BaseGenerator.cs" />
|
||||
<Compile Include="Generators\BoxWithHole.cs" />
|
||||
<Compile Include="Generators\CircleWithHole.cs" />
|
||||
<Compile Include="Generators\IGenerator.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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -103,6 +103,19 @@ namespace TriangleNet.Geometry
|
||||
ymax = Math.Max(ymax, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scale bounds.
|
||||
/// </summary>
|
||||
/// <param name="dx">Add dx to left and right bounds.</param>
|
||||
/// <param name="dy">Add dy to top and bottom bounds.</param>
|
||||
public void Scale(double dx, double dy)
|
||||
{
|
||||
xmin -= dx;
|
||||
xmax += dx;
|
||||
ymin -= dy;
|
||||
ymax += dy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if given point is inside bounding box.
|
||||
/// </summary>
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Start a new session with given name.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the session (and output files).</param>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Write complete mesh to a .mesh file.
|
||||
/// Write complete mesh to file.
|
||||
/// </summary>
|
||||
public void Write()
|
||||
public void Write(Mesh mesh, bool skip = false)
|
||||
{
|
||||
this.WriteMesh(mesh, skip);
|
||||
|
||||
this.triangles = mesh.Triangles.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finish this session.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-317
@@ -28,7 +28,6 @@ namespace TriangleNet
|
||||
ILog<SimpleLogItem> logger;
|
||||
|
||||
Quality quality;
|
||||
Sampler sampler;
|
||||
|
||||
// Stack that maintains a list of recently flipped triangles.
|
||||
Stack<Otri> 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
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Mesh" /> class.
|
||||
/// </summary>
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Find a triangle or edge containing a given point.
|
||||
/// </summary>
|
||||
/// <param name="searchpoint">The point to locate.</param>
|
||||
/// <param name="searchtri">The triangle to start the search at.</param>
|
||||
/// <param name="stopatsubsegment"> If 'stopatsubsegment' is set, the search
|
||||
/// will stop if it tries to walk through a subsegment, and will return OUTSIDE.</param>
|
||||
/// <returns>Location information.</returns>
|
||||
/// <remarks>
|
||||
/// 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.</remarks>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a triangle or edge containing a given point.
|
||||
/// </summary>
|
||||
/// <param name="searchpoint">The point to locate.</param>
|
||||
/// <param name="searchtri">The triangle to start the search at.</param>
|
||||
/// <returns>Location information.</returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
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<VoronoiRegion> regions;
|
||||
|
||||
// Stores the endpoints of rays of infinite Voronoi cells
|
||||
Dictionary<int, Point> rayPoints;
|
||||
int rayIndex;
|
||||
|
||||
// Bounding box of the triangles circumcenters.
|
||||
BoundingBox bounds;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Voronoi" /> class.
|
||||
/// </summary>
|
||||
@@ -74,11 +79,14 @@ namespace TriangleNet.Tools
|
||||
this.points = new Point[mesh.triangles.Count + mesh.hullsize];
|
||||
this.regions = new List<VoronoiRegion>(mesh.vertices.Count);
|
||||
|
||||
ComputeCircumCenters();
|
||||
|
||||
rayPoints = new Dictionary<int, Point>();
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Construct Voronoi region for given vertex.
|
||||
/// </summary>
|
||||
/// <param name="vertex"></param>
|
||||
/// <returns>The circumcenter indices which make up the cell.</returns>
|
||||
@@ -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)
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
<Compile Include="Algorithm\SweepLine.cs" />
|
||||
<Compile Include="Tools\Voronoi.cs" />
|
||||
<Compile Include="Tools\VoronoiRegion.cs" />
|
||||
<Compile Include="TriangleLocator.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="TriangleLocator.cs" company="">
|
||||
// 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/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace TriangleNet
|
||||
{
|
||||
using TriangleNet.Data;
|
||||
using TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Update summary.
|
||||
/// </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.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a triangle or edge containing a given point.
|
||||
/// </summary>
|
||||
/// <param name="searchpoint">The point to locate.</param>
|
||||
/// <param name="searchtri">The triangle to start the search at.</param>
|
||||
/// <param name="stopatsubsegment"> If 'stopatsubsegment' is set, the search
|
||||
/// will stop if it tries to walk through a subsegment, and will return OUTSIDE.</param>
|
||||
/// <returns>Location information.</returns>
|
||||
/// <remarks>
|
||||
/// 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.</remarks>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a triangle or edge containing a given point.
|
||||
/// </summary>
|
||||
/// <param name="searchpoint">The point to locate.</param>
|
||||
/// <param name="searchtri">The triangle to start the search at.</param>
|
||||
/// <returns>Location information.</returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user