More code reorganization (for beta 4)
git-svn-id: https://triangle.svn.codeplex.com/svn@75021 0e2699bc-83d4-4a8f-98e7-55e24ab8c7a5
This commit is contained in:
@@ -0,0 +1,900 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Dwyer.cs">
|
||||
// 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.Meshing.Algorithm
|
||||
{
|
||||
using System;
|
||||
using TriangleNet.Data;
|
||||
using TriangleNet.Log;
|
||||
|
||||
/// <summary>
|
||||
/// Builds a delaunay triangulation using the divide-and-conquer algorithm.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The divide-and-conquer bounding box
|
||||
///
|
||||
/// I originally implemented the divide-and-conquer and incremental Delaunay
|
||||
/// triangulations using the edge-based data structure presented by Guibas
|
||||
/// and Stolfi. Switching to a triangle-based data structure doubled the
|
||||
/// speed. However, I had to think of a few extra tricks to maintain the
|
||||
/// elegance of the original algorithms.
|
||||
///
|
||||
/// The "bounding box" used by my variant of the divide-and-conquer
|
||||
/// algorithm uses one triangle for each edge of the convex hull of the
|
||||
/// triangulation. These bounding triangles all share a common apical
|
||||
/// vertex, which is represented by NULL and which represents nothing.
|
||||
/// The bounding triangles are linked in a circular fan about this NULL
|
||||
/// vertex, and the edges on the convex hull of the triangulation appear
|
||||
/// opposite the NULL vertex. You might find it easiest to imagine that
|
||||
/// the NULL vertex is a point in 3D space behind the center of the
|
||||
/// triangulation, and that the bounding triangles form a sort of cone.
|
||||
///
|
||||
/// This bounding box makes it easy to represent degenerate cases. For
|
||||
/// instance, the triangulation of two vertices is a single edge. This edge
|
||||
/// is represented by two bounding box triangles, one on each "side" of the
|
||||
/// edge. These triangles are also linked together in a fan about the NULL
|
||||
/// vertex.
|
||||
///
|
||||
/// The bounding box also makes it easy to traverse the convex hull, as the
|
||||
/// divide-and-conquer algorithm needs to do.
|
||||
/// </remarks>
|
||||
class Dwyer
|
||||
{
|
||||
static Random rand = new Random(DateTime.Now.Millisecond);
|
||||
bool useDwyer = true;
|
||||
|
||||
Vertex[] sortarray;
|
||||
Mesh mesh;
|
||||
|
||||
/// <summary>
|
||||
/// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key.
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <remarks>
|
||||
/// Uses quicksort. Randomized O(n log n) time. No, I did not make any of
|
||||
/// the usual quicksort mistakes.
|
||||
/// </remarks>
|
||||
void VertexSort(int left, int right)
|
||||
{
|
||||
int oleft = left;
|
||||
int oright = right;
|
||||
int arraysize = right - left + 1;
|
||||
int pivot;
|
||||
double pivotx, pivoty;
|
||||
Vertex temp;
|
||||
|
||||
if (arraysize < 32)
|
||||
{
|
||||
// Insertion sort
|
||||
for (int i = left + 1; i <= right; i++)
|
||||
{
|
||||
var a = sortarray[i];
|
||||
int j = i - 1;
|
||||
while (j >= left && (sortarray[j].x > a.x || (sortarray[j].x == a.x && sortarray[j].y > a.y)))
|
||||
{
|
||||
sortarray[j + 1] = sortarray[j];
|
||||
j--;
|
||||
}
|
||||
sortarray[j + 1] = a;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Choose a random pivot to split the array.
|
||||
pivot = rand.Next(left, right);
|
||||
pivotx = sortarray[pivot].x;
|
||||
pivoty = sortarray[pivot].y;
|
||||
// Split the array.
|
||||
left--;
|
||||
right++;
|
||||
while (left < right)
|
||||
{
|
||||
// Search for a vertex whose x-coordinate is too large for the left.
|
||||
do
|
||||
{
|
||||
left++;
|
||||
}
|
||||
while ((left <= right) && ((sortarray[left].x < pivotx) ||
|
||||
((sortarray[left].x == pivotx) &&
|
||||
(sortarray[left].y < pivoty))));
|
||||
// Search for a vertex whose x-coordinate is too small for the right.
|
||||
do
|
||||
{
|
||||
right--;
|
||||
}
|
||||
while ((left <= right) && ((sortarray[right].x > pivotx) ||
|
||||
((sortarray[right].x == pivotx) &&
|
||||
(sortarray[right].y > pivoty))));
|
||||
|
||||
if (left < right)
|
||||
{
|
||||
// Swap the left and right vertices.
|
||||
temp = sortarray[left];
|
||||
sortarray[left] = sortarray[right];
|
||||
sortarray[right] = temp;
|
||||
}
|
||||
}
|
||||
if (left > oleft)
|
||||
{
|
||||
// Recursively sort the left subset.
|
||||
VertexSort(oleft, left);
|
||||
}
|
||||
if (oright > right + 1)
|
||||
{
|
||||
// Recursively sort the right subset.
|
||||
VertexSort(right + 1, oright);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An order statistic algorithm, almost. Shuffles an array of vertices so that
|
||||
/// the first 'median' vertices occur lexicographically before the remaining vertices.
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <param name="median"></param>
|
||||
/// <param name="axis"></param>
|
||||
/// <remarks>
|
||||
/// Uses the x-coordinate as the primary key if axis == 0; the y-coordinate
|
||||
/// if axis == 1. Very similar to the vertexsort() procedure, but runs in
|
||||
/// randomized linear time.
|
||||
/// </remarks>
|
||||
void VertexMedian(int left, int right, int median, int axis)
|
||||
{
|
||||
int arraysize = right - left + 1;
|
||||
int oleft = left, oright = right;
|
||||
int pivot;
|
||||
double pivot1, pivot2;
|
||||
Vertex temp;
|
||||
|
||||
if (arraysize == 2)
|
||||
{
|
||||
// Recursive base case.
|
||||
if ((sortarray[left][axis] > sortarray[right][axis]) ||
|
||||
((sortarray[left][axis] == sortarray[right][axis]) &&
|
||||
(sortarray[left][1 - axis] > sortarray[right][1 - axis])))
|
||||
{
|
||||
temp = sortarray[right];
|
||||
sortarray[right] = sortarray[left];
|
||||
sortarray[left] = temp;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Choose a random pivot to split the array.
|
||||
pivot = rand.Next(left, right); //left + arraysize / 2;
|
||||
pivot1 = sortarray[pivot][axis];
|
||||
pivot2 = sortarray[pivot][1 - axis];
|
||||
|
||||
left--;
|
||||
right++;
|
||||
while (left < right)
|
||||
{
|
||||
// Search for a vertex whose x-coordinate is too large for the left.
|
||||
do
|
||||
{
|
||||
left++;
|
||||
}
|
||||
while ((left <= right) && ((sortarray[left][axis] < pivot1) ||
|
||||
((sortarray[left][axis] == pivot1) &&
|
||||
(sortarray[left][1 - axis] < pivot2))));
|
||||
// Search for a vertex whose x-coordinate is too small for the right.
|
||||
do
|
||||
{
|
||||
right--;
|
||||
}
|
||||
while ((left <= right) && ((sortarray[right][axis] > pivot1) ||
|
||||
((sortarray[right][axis] == pivot1) &&
|
||||
(sortarray[right][1 - axis] > pivot2))));
|
||||
if (left < right)
|
||||
{
|
||||
// Swap the left and right vertices.
|
||||
temp = sortarray[left];
|
||||
sortarray[left] = sortarray[right];
|
||||
sortarray[right] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
// Unlike in vertexsort(), at most one of the following conditionals is true.
|
||||
if (left > median)
|
||||
{
|
||||
// Recursively shuffle the left subset.
|
||||
VertexMedian(oleft, left - 1, median, axis);
|
||||
}
|
||||
if (right < median - 1)
|
||||
{
|
||||
// Recursively shuffle the right subset.
|
||||
VertexMedian(right + 1, oright, median, axis);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts the vertices as appropriate for the divide-and-conquer algorithm with
|
||||
/// alternating cuts.
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <param name="axis"></param>
|
||||
/// <remarks>
|
||||
/// Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1.
|
||||
/// For the base case, subsets containing only two or three vertices are
|
||||
/// always sorted by x-coordinate.
|
||||
/// </remarks>
|
||||
void AlternateAxes(int left, int right, int axis)
|
||||
{
|
||||
int arraysize = right - left + 1;
|
||||
int divider;
|
||||
|
||||
divider = arraysize >> 1;
|
||||
//divider += left; // TODO: check
|
||||
if (arraysize <= 3)
|
||||
{
|
||||
// Recursive base case: subsets of two or three vertices will be
|
||||
// handled specially, and should always be sorted by x-coordinate.
|
||||
axis = 0;
|
||||
}
|
||||
// Partition with a horizontal or vertical cut.
|
||||
VertexMedian(left, right, left + divider, axis);
|
||||
// Recursively partition the subsets with a cross cut.
|
||||
if (arraysize - divider >= 2)
|
||||
{
|
||||
if (divider >= 2)
|
||||
{
|
||||
AlternateAxes(left, left + divider - 1, 1 - axis);
|
||||
}
|
||||
AlternateAxes(left + divider, right, 1 - axis);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation.
|
||||
/// </summary>
|
||||
/// <param name="farleft">Bounding triangles of the left triangulation.</param>
|
||||
/// <param name="innerleft">Bounding triangles of the left triangulation.</param>
|
||||
/// <param name="innerright">Bounding triangles of the right triangulation.</param>
|
||||
/// <param name="farright">Bounding triangles of the right triangulation.</param>
|
||||
/// <param name="axis"></param>
|
||||
/// <remarks>
|
||||
/// This is similar to the algorithm given by Guibas and Stolfi, but uses
|
||||
/// a triangle-based, rather than edge-based, data structure.
|
||||
///
|
||||
/// The algorithm walks up the gap between the two triangulations, knitting
|
||||
/// them together. As they are merged, some of their bounding triangles
|
||||
/// are converted into real triangles of the triangulation. The procedure
|
||||
/// pulls each hull's bounding triangles apart, then knits them together
|
||||
/// like the teeth of two gears. The Delaunay property determines, at each
|
||||
/// step, whether the next "tooth" is a bounding triangle of the left hull
|
||||
/// or the right. When a bounding triangle becomes real, its apex is
|
||||
/// changed from NULL to a real vertex.
|
||||
///
|
||||
/// Only two new triangles need to be allocated. These become new bounding
|
||||
/// triangles at the top and bottom of the seam. They are used to connect
|
||||
/// the remaining bounding triangles (those that have not been converted
|
||||
/// into real triangles) into a single fan.
|
||||
///
|
||||
/// On entry, 'farleft' and 'innerleft' are bounding triangles of the left
|
||||
/// triangulation. The origin of 'farleft' is the leftmost vertex, and
|
||||
/// the destination of 'innerleft' is the rightmost vertex of the
|
||||
/// triangulation. Similarly, 'innerright' and 'farright' are bounding
|
||||
/// triangles of the right triangulation. The origin of 'innerright' and
|
||||
/// destination of 'farright' are the leftmost and rightmost vertices.
|
||||
///
|
||||
/// On completion, the origin of 'farleft' is the leftmost vertex of the
|
||||
/// merged triangulation, and the destination of 'farright' is the rightmost
|
||||
/// vertex.
|
||||
/// </remarks>
|
||||
void MergeHulls(ref Otri farleft, ref Otri innerleft, ref Otri innerright,
|
||||
ref Otri farright, int axis)
|
||||
{
|
||||
Otri leftcand = default(Otri), rightcand = default(Otri);
|
||||
Otri nextedge = default(Otri);
|
||||
Otri sidecasing = default(Otri), topcasing = default(Otri), outercasing = default(Otri);
|
||||
Otri checkedge = default(Otri);
|
||||
Otri baseedge = default(Otri);
|
||||
Vertex innerleftdest;
|
||||
Vertex innerrightorg;
|
||||
Vertex innerleftapex, innerrightapex;
|
||||
Vertex farleftpt, farrightpt;
|
||||
Vertex farleftapex, farrightapex;
|
||||
Vertex lowerleft, lowerright;
|
||||
Vertex upperleft, upperright;
|
||||
Vertex nextapex;
|
||||
Vertex checkvertex;
|
||||
bool changemade;
|
||||
bool badedge;
|
||||
bool leftfinished, rightfinished;
|
||||
|
||||
innerleftdest = innerleft.Dest();
|
||||
innerleftapex = innerleft.Apex();
|
||||
innerrightorg = innerright.Org();
|
||||
innerrightapex = innerright.Apex();
|
||||
// Special treatment for horizontal cuts.
|
||||
if (useDwyer && (axis == 1))
|
||||
{
|
||||
farleftpt = farleft.Org();
|
||||
farleftapex = farleft.Apex();
|
||||
farrightpt = farright.Dest();
|
||||
farrightapex = farright.Apex();
|
||||
// The pointers to the extremal vertices are shifted to point to the
|
||||
// topmost and bottommost vertex of each hull, rather than the
|
||||
// leftmost and rightmost vertices.
|
||||
while (farleftapex.y < farleftpt.y)
|
||||
{
|
||||
farleft.LnextSelf();
|
||||
farleft.SymSelf();
|
||||
farleftpt = farleftapex;
|
||||
farleftapex = farleft.Apex();
|
||||
}
|
||||
innerleft.Sym(ref checkedge);
|
||||
checkvertex = checkedge.Apex();
|
||||
while (checkvertex.y > innerleftdest.y)
|
||||
{
|
||||
checkedge.Lnext(ref innerleft);
|
||||
innerleftapex = innerleftdest;
|
||||
innerleftdest = checkvertex;
|
||||
innerleft.Sym(ref checkedge);
|
||||
checkvertex = checkedge.Apex();
|
||||
}
|
||||
while (innerrightapex.y < innerrightorg.y)
|
||||
{
|
||||
innerright.LnextSelf();
|
||||
innerright.SymSelf();
|
||||
innerrightorg = innerrightapex;
|
||||
innerrightapex = innerright.Apex();
|
||||
}
|
||||
farright.Sym(ref checkedge);
|
||||
checkvertex = checkedge.Apex();
|
||||
while (checkvertex.y > farrightpt.y)
|
||||
{
|
||||
checkedge.Lnext(ref farright);
|
||||
farrightapex = farrightpt;
|
||||
farrightpt = checkvertex;
|
||||
farright.Sym(ref checkedge);
|
||||
checkvertex = checkedge.Apex();
|
||||
}
|
||||
}
|
||||
// Find a line tangent to and below both hulls.
|
||||
do
|
||||
{
|
||||
changemade = false;
|
||||
// Make innerleftdest the "bottommost" vertex of the left hull.
|
||||
if (Primitives.CounterClockwise(innerleftdest, innerleftapex, innerrightorg) > 0.0)
|
||||
{
|
||||
innerleft.LprevSelf();
|
||||
innerleft.SymSelf();
|
||||
innerleftdest = innerleftapex;
|
||||
innerleftapex = innerleft.Apex();
|
||||
changemade = true;
|
||||
}
|
||||
// Make innerrightorg the "bottommost" vertex of the right hull.
|
||||
if (Primitives.CounterClockwise(innerrightapex, innerrightorg, innerleftdest) > 0.0)
|
||||
{
|
||||
innerright.LnextSelf();
|
||||
innerright.SymSelf();
|
||||
innerrightorg = innerrightapex;
|
||||
innerrightapex = innerright.Apex();
|
||||
changemade = true;
|
||||
}
|
||||
} while (changemade);
|
||||
|
||||
// Find the two candidates to be the next "gear tooth."
|
||||
innerleft.Sym(ref leftcand);
|
||||
innerright.Sym(ref rightcand);
|
||||
// Create the bottom new bounding triangle.
|
||||
mesh.MakeTriangle(ref baseedge);
|
||||
// Connect it to the bounding boxes of the left and right triangulations.
|
||||
baseedge.Bond(ref innerleft);
|
||||
baseedge.LnextSelf();
|
||||
baseedge.Bond(ref innerright);
|
||||
baseedge.LnextSelf();
|
||||
baseedge.SetOrg(innerrightorg);
|
||||
baseedge.SetDest(innerleftdest);
|
||||
// Apex is intentionally left NULL.
|
||||
|
||||
// Fix the extreme triangles if necessary.
|
||||
farleftpt = farleft.Org();
|
||||
if (innerleftdest == farleftpt)
|
||||
{
|
||||
baseedge.Lnext(ref farleft);
|
||||
}
|
||||
farrightpt = farright.Dest();
|
||||
if (innerrightorg == farrightpt)
|
||||
{
|
||||
baseedge.Lprev(ref farright);
|
||||
}
|
||||
// The vertices of the current knitting edge.
|
||||
lowerleft = innerleftdest;
|
||||
lowerright = innerrightorg;
|
||||
// The candidate vertices for knitting.
|
||||
upperleft = leftcand.Apex();
|
||||
upperright = rightcand.Apex();
|
||||
// Walk up the gap between the two triangulations, knitting them together.
|
||||
while (true)
|
||||
{
|
||||
// Have we reached the top? (This isn't quite the right question,
|
||||
// because even though the left triangulation might seem finished now,
|
||||
// moving up on the right triangulation might reveal a new vertex of
|
||||
// the left triangulation. And vice-versa.)
|
||||
leftfinished = Primitives.CounterClockwise(upperleft, lowerleft, lowerright) <= 0.0;
|
||||
rightfinished = Primitives.CounterClockwise(upperright, lowerleft, lowerright) <= 0.0;
|
||||
if (leftfinished && rightfinished)
|
||||
{
|
||||
// Create the top new bounding triangle.
|
||||
mesh.MakeTriangle(ref nextedge);
|
||||
nextedge.SetOrg(lowerleft);
|
||||
nextedge.SetDest(lowerright);
|
||||
// Apex is intentionally left NULL.
|
||||
// Connect it to the bounding boxes of the two triangulations.
|
||||
nextedge.Bond(ref baseedge);
|
||||
nextedge.LnextSelf();
|
||||
nextedge.Bond(ref rightcand);
|
||||
nextedge.LnextSelf();
|
||||
nextedge.Bond(ref leftcand);
|
||||
|
||||
// Special treatment for horizontal cuts.
|
||||
if (useDwyer && (axis == 1))
|
||||
{
|
||||
farleftpt = farleft.Org();
|
||||
farleftapex = farleft.Apex();
|
||||
farrightpt = farright.Dest();
|
||||
farrightapex = farright.Apex();
|
||||
farleft.Sym(ref checkedge);
|
||||
checkvertex = checkedge.Apex();
|
||||
// The pointers to the extremal vertices are restored to the
|
||||
// leftmost and rightmost vertices (rather than topmost and
|
||||
// bottommost).
|
||||
while (checkvertex.x < farleftpt.x)
|
||||
{
|
||||
checkedge.Lprev(ref farleft);
|
||||
farleftapex = farleftpt;
|
||||
farleftpt = checkvertex;
|
||||
farleft.Sym(ref checkedge);
|
||||
checkvertex = checkedge.Apex();
|
||||
}
|
||||
while (farrightapex.x > farrightpt.x)
|
||||
{
|
||||
farright.LprevSelf();
|
||||
farright.SymSelf();
|
||||
farrightpt = farrightapex;
|
||||
farrightapex = farright.Apex();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Consider eliminating edges from the left triangulation.
|
||||
if (!leftfinished)
|
||||
{
|
||||
// What vertex would be exposed if an edge were deleted?
|
||||
leftcand.Lprev(ref nextedge);
|
||||
nextedge.SymSelf();
|
||||
nextapex = nextedge.Apex();
|
||||
// If nextapex is NULL, then no vertex would be exposed; the
|
||||
// triangulation would have been eaten right through.
|
||||
if (nextapex != null)
|
||||
{
|
||||
// Check whether the edge is Delaunay.
|
||||
badedge = Primitives.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0;
|
||||
while (badedge)
|
||||
{
|
||||
// Eliminate the edge with an edge flip. As a result, the
|
||||
// left triangulation will have one more boundary triangle.
|
||||
nextedge.LnextSelf();
|
||||
nextedge.Sym(ref topcasing);
|
||||
nextedge.LnextSelf();
|
||||
nextedge.Sym(ref sidecasing);
|
||||
nextedge.Bond(ref topcasing);
|
||||
leftcand.Bond(ref sidecasing);
|
||||
leftcand.LnextSelf();
|
||||
leftcand.Sym(ref outercasing);
|
||||
nextedge.LprevSelf();
|
||||
nextedge.Bond(ref outercasing);
|
||||
// Correct the vertices to reflect the edge flip.
|
||||
leftcand.SetOrg(lowerleft);
|
||||
leftcand.SetDest(null);
|
||||
leftcand.SetApex(nextapex);
|
||||
nextedge.SetOrg(null);
|
||||
nextedge.SetDest(upperleft);
|
||||
nextedge.SetApex(nextapex);
|
||||
// Consider the newly exposed vertex.
|
||||
upperleft = nextapex;
|
||||
// What vertex would be exposed if another edge were deleted?
|
||||
sidecasing.Copy(ref nextedge);
|
||||
nextapex = nextedge.Apex();
|
||||
if (nextapex != null)
|
||||
{
|
||||
// Check whether the edge is Delaunay.
|
||||
badedge = Primitives.InCircle(lowerleft, lowerright, upperleft, nextapex) > 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Avoid eating right through the triangulation.
|
||||
badedge = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Consider eliminating edges from the right triangulation.
|
||||
if (!rightfinished)
|
||||
{
|
||||
// What vertex would be exposed if an edge were deleted?
|
||||
rightcand.Lnext(ref nextedge);
|
||||
nextedge.SymSelf();
|
||||
nextapex = nextedge.Apex();
|
||||
// If nextapex is NULL, then no vertex would be exposed; the
|
||||
// triangulation would have been eaten right through.
|
||||
if (nextapex != null)
|
||||
{
|
||||
// Check whether the edge is Delaunay.
|
||||
badedge = Primitives.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0;
|
||||
while (badedge)
|
||||
{
|
||||
// Eliminate the edge with an edge flip. As a result, the
|
||||
// right triangulation will have one more boundary triangle.
|
||||
nextedge.LprevSelf();
|
||||
nextedge.Sym(ref topcasing);
|
||||
nextedge.LprevSelf();
|
||||
nextedge.Sym(ref sidecasing);
|
||||
nextedge.Bond(ref topcasing);
|
||||
rightcand.Bond(ref sidecasing);
|
||||
rightcand.LprevSelf();
|
||||
rightcand.Sym(ref outercasing);
|
||||
nextedge.LnextSelf();
|
||||
nextedge.Bond(ref outercasing);
|
||||
// Correct the vertices to reflect the edge flip.
|
||||
rightcand.SetOrg(null);
|
||||
rightcand.SetDest(lowerright);
|
||||
rightcand.SetApex(nextapex);
|
||||
nextedge.SetOrg(upperright);
|
||||
nextedge.SetDest(null);
|
||||
nextedge.SetApex(nextapex);
|
||||
// Consider the newly exposed vertex.
|
||||
upperright = nextapex;
|
||||
// What vertex would be exposed if another edge were deleted?
|
||||
sidecasing.Copy(ref nextedge);
|
||||
nextapex = nextedge.Apex();
|
||||
if (nextapex != null)
|
||||
{
|
||||
// Check whether the edge is Delaunay.
|
||||
badedge = Primitives.InCircle(lowerleft, lowerright, upperright, nextapex) > 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Avoid eating right through the triangulation.
|
||||
badedge = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (leftfinished || (!rightfinished &&
|
||||
(Primitives.InCircle(upperleft, lowerleft, lowerright, upperright) > 0.0)))
|
||||
{
|
||||
// Knit the triangulations, adding an edge from 'lowerleft'
|
||||
// to 'upperright'.
|
||||
baseedge.Bond(ref rightcand);
|
||||
rightcand.Lprev(ref baseedge);
|
||||
baseedge.SetDest(lowerleft);
|
||||
lowerright = upperright;
|
||||
baseedge.Sym(ref rightcand);
|
||||
upperright = rightcand.Apex();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Knit the triangulations, adding an edge from 'upperleft'
|
||||
// to 'lowerright'.
|
||||
baseedge.Bond(ref leftcand);
|
||||
leftcand.Lnext(ref baseedge);
|
||||
baseedge.SetOrg(lowerright);
|
||||
lowerleft = upperleft;
|
||||
baseedge.Sym(ref leftcand);
|
||||
upperleft = leftcand.Apex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively form a Delaunay triangulation by the divide-and-conquer method.
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <param name="axis"></param>
|
||||
/// <param name="farleft"></param>
|
||||
/// <param name="farright"></param>
|
||||
/// <remarks>
|
||||
/// Recursively breaks down the problem into smaller pieces, which are
|
||||
/// knitted together by mergehulls(). The base cases (problems of two or
|
||||
/// three vertices) are handled specially here.
|
||||
///
|
||||
/// On completion, 'farleft' and 'farright' are bounding triangles such that
|
||||
/// the origin of 'farleft' is the leftmost vertex (breaking ties by
|
||||
/// choosing the highest leftmost vertex), and the destination of
|
||||
/// 'farright' is the rightmost vertex (breaking ties by choosing the
|
||||
/// lowest rightmost vertex).
|
||||
/// </remarks>
|
||||
void DivconqRecurse(int left, int right, int axis,
|
||||
ref Otri farleft, ref Otri farright)
|
||||
{
|
||||
Otri midtri = default(Otri);
|
||||
Otri tri1 = default(Otri);
|
||||
Otri tri2 = default(Otri);
|
||||
Otri tri3 = default(Otri);
|
||||
Otri innerleft = default(Otri), innerright = default(Otri);
|
||||
double area;
|
||||
int vertices = right - left + 1;
|
||||
int divider;
|
||||
|
||||
if (vertices == 2)
|
||||
{
|
||||
// The triangulation of two vertices is an edge. An edge is
|
||||
// represented by two bounding triangles.
|
||||
mesh.MakeTriangle(ref farleft);
|
||||
farleft.SetOrg(sortarray[left]);
|
||||
farleft.SetDest(sortarray[left + 1]);
|
||||
// The apex is intentionally left NULL.
|
||||
mesh.MakeTriangle(ref farright);
|
||||
farright.SetOrg(sortarray[left + 1]);
|
||||
farright.SetDest(sortarray[left]);
|
||||
// The apex is intentionally left NULL.
|
||||
farleft.Bond(ref farright);
|
||||
farleft.LprevSelf();
|
||||
farright.LnextSelf();
|
||||
farleft.Bond(ref farright);
|
||||
farleft.LprevSelf();
|
||||
farright.LnextSelf();
|
||||
farleft.Bond(ref farright);
|
||||
|
||||
// Ensure that the origin of 'farleft' is sortarray[0].
|
||||
farright.Lprev(ref farleft);
|
||||
return;
|
||||
}
|
||||
else if (vertices == 3)
|
||||
{
|
||||
// The triangulation of three vertices is either a triangle (with
|
||||
// three bounding triangles) or two edges (with four bounding
|
||||
// triangles). In either case, four triangles are created.
|
||||
mesh.MakeTriangle(ref midtri);
|
||||
mesh.MakeTriangle(ref tri1);
|
||||
mesh.MakeTriangle(ref tri2);
|
||||
mesh.MakeTriangle(ref tri3);
|
||||
area = Primitives.CounterClockwise(sortarray[left], sortarray[left + 1], sortarray[left + 2]);
|
||||
if (area == 0.0)
|
||||
{
|
||||
// Three collinear vertices; the triangulation is two edges.
|
||||
midtri.SetOrg(sortarray[left]);
|
||||
midtri.SetDest(sortarray[left + 1]);
|
||||
tri1.SetOrg(sortarray[left + 1]);
|
||||
tri1.SetDest(sortarray[left]);
|
||||
tri2.SetOrg(sortarray[left + 2]);
|
||||
tri2.SetDest(sortarray[left + 1]);
|
||||
tri3.SetOrg(sortarray[left + 1]);
|
||||
tri3.SetDest(sortarray[left + 2]);
|
||||
// All apices are intentionally left NULL.
|
||||
midtri.Bond(ref tri1);
|
||||
tri2.Bond(ref tri3);
|
||||
midtri.LnextSelf();
|
||||
tri1.LprevSelf();
|
||||
tri2.LnextSelf();
|
||||
tri3.LprevSelf();
|
||||
midtri.Bond(ref tri3);
|
||||
tri1.Bond(ref tri2);
|
||||
midtri.LnextSelf();
|
||||
tri1.LprevSelf();
|
||||
tri2.LnextSelf();
|
||||
tri3.LprevSelf();
|
||||
midtri.Bond(ref tri1);
|
||||
tri2.Bond(ref tri3);
|
||||
// Ensure that the origin of 'farleft' is sortarray[0].
|
||||
tri1.Copy(ref farleft);
|
||||
// Ensure that the destination of 'farright' is sortarray[2].
|
||||
tri2.Copy(ref farright);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The three vertices are not collinear; the triangulation is one
|
||||
// triangle, namely 'midtri'.
|
||||
midtri.SetOrg(sortarray[left]);
|
||||
tri1.SetDest(sortarray[left]);
|
||||
tri3.SetOrg(sortarray[left]);
|
||||
// Apices of tri1, tri2, and tri3 are left NULL.
|
||||
if (area > 0.0)
|
||||
{
|
||||
// The vertices are in counterclockwise order.
|
||||
midtri.SetDest(sortarray[left + 1]);
|
||||
tri1.SetOrg(sortarray[left + 1]);
|
||||
tri2.SetDest(sortarray[left + 1]);
|
||||
midtri.SetApex(sortarray[left + 2]);
|
||||
tri2.SetOrg(sortarray[left + 2]);
|
||||
tri3.SetDest(sortarray[left + 2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The vertices are in clockwise order.
|
||||
midtri.SetDest(sortarray[left + 2]);
|
||||
tri1.SetOrg(sortarray[left + 2]);
|
||||
tri2.SetDest(sortarray[left + 2]);
|
||||
midtri.SetApex(sortarray[left + 1]);
|
||||
tri2.SetOrg(sortarray[left + 1]);
|
||||
tri3.SetDest(sortarray[left + 1]);
|
||||
}
|
||||
// The topology does not depend on how the vertices are ordered.
|
||||
midtri.Bond(ref tri1);
|
||||
midtri.LnextSelf();
|
||||
midtri.Bond(ref tri2);
|
||||
midtri.LnextSelf();
|
||||
midtri.Bond(ref tri3);
|
||||
tri1.LprevSelf();
|
||||
tri2.LnextSelf();
|
||||
tri1.Bond(ref tri2);
|
||||
tri1.LprevSelf();
|
||||
tri3.LprevSelf();
|
||||
tri1.Bond(ref tri3);
|
||||
tri2.LnextSelf();
|
||||
tri3.LprevSelf();
|
||||
tri2.Bond(ref tri3);
|
||||
// Ensure that the origin of 'farleft' is sortarray[0].
|
||||
tri1.Copy(ref farleft);
|
||||
// Ensure that the destination of 'farright' is sortarray[2].
|
||||
if (area > 0.0)
|
||||
{
|
||||
tri2.Copy(ref farright);
|
||||
}
|
||||
else
|
||||
{
|
||||
farleft.Lnext(ref farright);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split the vertices in half.
|
||||
divider = vertices >> 1;
|
||||
// Recursively triangulate each half.
|
||||
DivconqRecurse(left, left + divider - 1, 1 - axis, ref farleft, ref innerleft);
|
||||
//DebugWriter.Session.Write(mesh, true);
|
||||
DivconqRecurse(left + divider, right, 1 - axis, ref innerright, ref farright);
|
||||
//DebugWriter.Session.Write(mesh, true);
|
||||
|
||||
// Merge the two triangulations into one.
|
||||
MergeHulls(ref farleft, ref innerleft, ref innerright, ref farright, axis);
|
||||
//DebugWriter.Session.Write(mesh, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes ghost triangles.
|
||||
/// </summary>
|
||||
/// <param name="startghost"></param>
|
||||
/// <returns>Number of vertices on the hull.</returns>
|
||||
int RemoveGhosts(ref Otri startghost)
|
||||
{
|
||||
Otri searchedge = default(Otri);
|
||||
Otri dissolveedge = default(Otri);
|
||||
Otri deadtriangle = default(Otri);
|
||||
Vertex markorg;
|
||||
|
||||
int hullsize;
|
||||
|
||||
bool noPoly = !mesh.behavior.Poly;
|
||||
|
||||
// Find an edge on the convex hull to start point location from.
|
||||
startghost.Lprev(ref searchedge);
|
||||
searchedge.SymSelf();
|
||||
Mesh.dummytri.neighbors[0] = searchedge;
|
||||
// Remove the bounding box and count the convex hull edges.
|
||||
startghost.Copy(ref dissolveedge);
|
||||
hullsize = 0;
|
||||
do
|
||||
{
|
||||
hullsize++;
|
||||
dissolveedge.Lnext(ref deadtriangle);
|
||||
dissolveedge.LprevSelf();
|
||||
dissolveedge.SymSelf();
|
||||
|
||||
// If no PSLG is involved, set the boundary markers of all the vertices
|
||||
// on the convex hull. If a PSLG is used, this step is done later.
|
||||
if (noPoly)
|
||||
{
|
||||
// Watch out for the case where all the input vertices are collinear.
|
||||
if (dissolveedge.triangle != Mesh.dummytri)
|
||||
{
|
||||
markorg = dissolveedge.Org();
|
||||
if (markorg.mark == 0)
|
||||
{
|
||||
markorg.mark = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove a bounding triangle from a convex hull triangle.
|
||||
dissolveedge.Dissolve();
|
||||
// Find the next bounding triangle.
|
||||
deadtriangle.Sym(ref dissolveedge);
|
||||
|
||||
// Delete the bounding triangle.
|
||||
mesh.TriangleDealloc(deadtriangle.triangle);
|
||||
} while (!dissolveedge.Equal(startghost));
|
||||
|
||||
return hullsize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Form a Delaunay triangulation by the divide-and-conquer method.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Sorts the vertices, calls a recursive procedure to triangulate them, and
|
||||
/// removes the bounding box, setting boundary markers as appropriate.
|
||||
/// </remarks>
|
||||
public int Triangulate(Mesh m)
|
||||
{
|
||||
Otri hullleft = default(Otri), hullright = default(Otri);
|
||||
int divider;
|
||||
int i, j;
|
||||
|
||||
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];
|
||||
i = 0;
|
||||
foreach (var v in m.vertices.Values)
|
||||
{
|
||||
sortarray[i++] = v;
|
||||
}
|
||||
// Sort the vertices.
|
||||
//Array.Sort(sortarray);
|
||||
VertexSort(0, m.invertices - 1);
|
||||
// Discard duplicate vertices, which can really mess up the algorithm.
|
||||
i = 0;
|
||||
for (j = 1; j < m.invertices; j++)
|
||||
{
|
||||
if ((sortarray[i].x == sortarray[j].x)
|
||||
&& (sortarray[i].y == sortarray[j].y))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
SimpleLog.Instance.Warning(
|
||||
String.Format("A duplicate vertex appeared and was ignored (ID {0}).", sortarray[j].hash),
|
||||
"DivConquer.DivconqDelaunay()");
|
||||
}
|
||||
sortarray[j].type = VertexType.UndeadVertex;
|
||||
m.undeads++;
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
sortarray[i] = sortarray[j];
|
||||
}
|
||||
}
|
||||
i++;
|
||||
if (useDwyer)
|
||||
{
|
||||
// Re-sort the array of vertices to accommodate alternating cuts.
|
||||
divider = i >> 1;
|
||||
if (i - divider >= 2)
|
||||
{
|
||||
if (divider >= 2)
|
||||
{
|
||||
AlternateAxes(0, divider - 1, 1);
|
||||
}
|
||||
AlternateAxes(divider, i - 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Form the Delaunay triangulation.
|
||||
DivconqRecurse(0, i-1, 0, ref hullleft, ref hullright);
|
||||
|
||||
//DebugWriter.Session.Write(mesh);
|
||||
//DebugWriter.Session.Finish();
|
||||
|
||||
return RemoveGhosts(ref hullleft);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Incremental.cs">
|
||||
// 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.Meshing.Algorithm
|
||||
{
|
||||
using TriangleNet.Data;
|
||||
using TriangleNet.Log;
|
||||
using TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Builds a delaunay triangulation using the incremental algorithm.
|
||||
/// </summary>
|
||||
class Incremental
|
||||
{
|
||||
Mesh mesh;
|
||||
|
||||
/// <summary>
|
||||
/// Form an "infinite" bounding triangle to insert vertices into.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The vertices at "infinity" are assigned finite coordinates, which are
|
||||
/// used by the point location routines, but (mostly) ignored by the
|
||||
/// Delaunay edge flip routines.
|
||||
/// </remarks>
|
||||
void GetBoundingBox()
|
||||
{
|
||||
Otri inftri = default(Otri); // Handle for the triangular bounding box.
|
||||
Rectangle box = mesh.bounds;
|
||||
|
||||
// Find the width (or height, whichever is larger) of the triangulation.
|
||||
double width = box.Width;
|
||||
if (box.Height > width)
|
||||
{
|
||||
width = box.Height;
|
||||
}
|
||||
if (width == 0.0)
|
||||
{
|
||||
width = 1.0;
|
||||
}
|
||||
// Create the vertices of the bounding box.
|
||||
mesh.infvertex1 = new Vertex(box.Left - 50.0 * width, box.Bottom - 40.0 * width);
|
||||
mesh.infvertex2 = new Vertex(box.Right + 50.0 * width, box.Bottom - 40.0 * width);
|
||||
mesh.infvertex3 = new Vertex(0.5 * (box.Left + box.Right), box.Top + 60.0 * width);
|
||||
|
||||
// Create the bounding box.
|
||||
mesh.MakeTriangle(ref inftri);
|
||||
inftri.SetOrg(mesh.infvertex1);
|
||||
inftri.SetDest(mesh.infvertex2);
|
||||
inftri.SetApex(mesh.infvertex3);
|
||||
// Link dummytri to the bounding box so we can always find an
|
||||
// edge to begin searching (point location) from.
|
||||
Mesh.dummytri.neighbors[0] = inftri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove the "infinite" bounding triangle, setting boundary markers as appropriate.
|
||||
/// </summary>
|
||||
/// <returns>Returns the number of edges on the convex hull of the triangulation.</returns>
|
||||
/// <remarks>
|
||||
/// The triangular bounding box has three boundary triangles (one for each
|
||||
/// side of the bounding box), and a bunch of triangles fanning out from
|
||||
/// the three bounding box vertices (one triangle for each edge of the
|
||||
/// convex hull of the inner mesh). This routine removes these triangles.
|
||||
/// </remarks>
|
||||
int RemoveBox()
|
||||
{
|
||||
Otri deadtriangle = default(Otri);
|
||||
Otri searchedge = default(Otri);
|
||||
Otri checkedge = default(Otri);
|
||||
Otri nextedge = default(Otri), finaledge = default(Otri), dissolveedge = default(Otri);
|
||||
Vertex markorg;
|
||||
int hullsize;
|
||||
|
||||
bool noPoly = !mesh.behavior.Poly;
|
||||
|
||||
// Find a boundary triangle.
|
||||
nextedge.triangle = Mesh.dummytri;
|
||||
nextedge.orient = 0;
|
||||
nextedge.SymSelf();
|
||||
// Mark a place to stop.
|
||||
nextedge.Lprev(ref finaledge);
|
||||
nextedge.LnextSelf();
|
||||
nextedge.SymSelf();
|
||||
// Find a triangle (on the boundary of the vertex set) that isn't
|
||||
// a bounding box triangle.
|
||||
nextedge.Lprev(ref searchedge);
|
||||
searchedge.SymSelf();
|
||||
// Check whether nextedge is another boundary triangle
|
||||
// adjacent to the first one.
|
||||
nextedge.Lnext(ref checkedge);
|
||||
checkedge.SymSelf();
|
||||
if (checkedge.triangle == Mesh.dummytri)
|
||||
{
|
||||
// Go on to the next triangle. There are only three boundary
|
||||
// triangles, and this next triangle cannot be the third one,
|
||||
// so it's safe to stop here.
|
||||
searchedge.LprevSelf();
|
||||
searchedge.SymSelf();
|
||||
}
|
||||
// Find a new boundary edge to search from, as the current search
|
||||
// edge lies on a bounding box triangle and will be deleted.
|
||||
Mesh.dummytri.neighbors[0] = searchedge;
|
||||
hullsize = -2;
|
||||
while (!nextedge.Equal(finaledge))
|
||||
{
|
||||
hullsize++;
|
||||
nextedge.Lprev(ref dissolveedge);
|
||||
dissolveedge.SymSelf();
|
||||
// If not using a PSLG, the vertices should be marked now.
|
||||
// (If using a PSLG, markhull() will do the job.)
|
||||
if (noPoly)
|
||||
{
|
||||
// Be careful! One must check for the case where all the input
|
||||
// vertices are collinear, and thus all the triangles are part of
|
||||
// the bounding box. Otherwise, the setvertexmark() call below
|
||||
// will cause a bad pointer reference.
|
||||
if (dissolveedge.triangle != Mesh.dummytri)
|
||||
{
|
||||
markorg = dissolveedge.Org();
|
||||
if (markorg.mark == 0)
|
||||
{
|
||||
markorg.mark = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Disconnect the bounding box triangle from the mesh triangle.
|
||||
dissolveedge.Dissolve();
|
||||
nextedge.Lnext(ref deadtriangle);
|
||||
deadtriangle.Sym(ref nextedge);
|
||||
// Get rid of the bounding box triangle.
|
||||
mesh.TriangleDealloc(deadtriangle.triangle);
|
||||
// Do we need to turn the corner?
|
||||
if (nextedge.triangle == Mesh.dummytri)
|
||||
{
|
||||
// Turn the corner.
|
||||
dissolveedge.Copy(ref nextedge);
|
||||
}
|
||||
}
|
||||
mesh.TriangleDealloc(finaledge.triangle);
|
||||
|
||||
return hullsize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Form a Delaunay triangulation by incrementally inserting vertices.
|
||||
/// </summary>
|
||||
/// <returns>Returns the number of edges on the convex hull of the
|
||||
/// triangulation.</returns>
|
||||
public int Triangulate(Mesh mesh)
|
||||
{
|
||||
this.mesh = mesh;
|
||||
|
||||
Otri starttri = new Otri();
|
||||
|
||||
// Create a triangular bounding box.
|
||||
GetBoundingBox();
|
||||
|
||||
foreach (var v in mesh.vertices.Values)
|
||||
{
|
||||
starttri.triangle = Mesh.dummytri;
|
||||
Osub tmp = default(Osub);
|
||||
if (mesh.InsertVertex(v, ref starttri, ref tmp, false, false) == InsertVertexResult.Duplicate)
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
SimpleLog.Instance.Warning("A duplicate vertex appeared and was ignored.",
|
||||
"Incremental.IncrementalDelaunay()");
|
||||
}
|
||||
v.type = VertexType.UndeadVertex;
|
||||
mesh.undeads++;
|
||||
}
|
||||
}
|
||||
// Remove the bounding box.
|
||||
return RemoveBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,799 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="SweepLine.cs">
|
||||
// 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.Meshing.Algorithm
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TriangleNet.Data;
|
||||
using TriangleNet.Geometry;
|
||||
using TriangleNet.Log;
|
||||
using TriangleNet.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// Builds a delaunay triangulation using the sweepline algorithm.
|
||||
/// </summary>
|
||||
class SweepLine
|
||||
{
|
||||
static int randomseed = 1;
|
||||
static int SAMPLERATE = 10;
|
||||
|
||||
int randomnation(int choices)
|
||||
{
|
||||
randomseed = (randomseed * 1366 + 150889) % 714025;
|
||||
return randomseed / (714025 / choices + 1);
|
||||
}
|
||||
|
||||
Mesh mesh;
|
||||
double xminextreme; // Nonexistent x value used as a flag in sweepline.
|
||||
List<SplayNode> splaynodes;
|
||||
|
||||
#region Heap
|
||||
|
||||
void HeapInsert(SweepEvent[] heap, int heapsize, SweepEvent newevent)
|
||||
{
|
||||
double eventx, eventy;
|
||||
int eventnum;
|
||||
int parent;
|
||||
bool notdone;
|
||||
|
||||
eventx = newevent.xkey;
|
||||
eventy = newevent.ykey;
|
||||
eventnum = heapsize;
|
||||
notdone = eventnum > 0;
|
||||
while (notdone)
|
||||
{
|
||||
parent = (eventnum - 1) >> 1;
|
||||
if ((heap[parent].ykey < eventy) ||
|
||||
((heap[parent].ykey == eventy)
|
||||
&& (heap[parent].xkey <= eventx)))
|
||||
{
|
||||
notdone = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
heap[eventnum] = heap[parent];
|
||||
heap[eventnum].heapposition = eventnum;
|
||||
|
||||
eventnum = parent;
|
||||
notdone = eventnum > 0;
|
||||
}
|
||||
}
|
||||
heap[eventnum] = newevent;
|
||||
newevent.heapposition = eventnum;
|
||||
}
|
||||
|
||||
void Heapify(SweepEvent[] heap, int heapsize, int eventnum)
|
||||
{
|
||||
SweepEvent thisevent;
|
||||
double eventx, eventy;
|
||||
int leftchild, rightchild;
|
||||
int smallest;
|
||||
bool notdone;
|
||||
|
||||
thisevent = heap[eventnum];
|
||||
eventx = thisevent.xkey;
|
||||
eventy = thisevent.ykey;
|
||||
leftchild = 2 * eventnum + 1;
|
||||
notdone = leftchild < heapsize;
|
||||
while (notdone)
|
||||
{
|
||||
if ((heap[leftchild].ykey < eventy) ||
|
||||
((heap[leftchild].ykey == eventy)
|
||||
&& (heap[leftchild].xkey < eventx)))
|
||||
{
|
||||
smallest = leftchild;
|
||||
}
|
||||
else
|
||||
{
|
||||
smallest = eventnum;
|
||||
}
|
||||
rightchild = leftchild + 1;
|
||||
if (rightchild < heapsize)
|
||||
{
|
||||
if ((heap[rightchild].ykey < heap[smallest].ykey) ||
|
||||
((heap[rightchild].ykey == heap[smallest].ykey)
|
||||
&& (heap[rightchild].xkey < heap[smallest].xkey)))
|
||||
{
|
||||
smallest = rightchild;
|
||||
}
|
||||
}
|
||||
if (smallest == eventnum)
|
||||
{
|
||||
notdone = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
heap[eventnum] = heap[smallest];
|
||||
heap[eventnum].heapposition = eventnum;
|
||||
heap[smallest] = thisevent;
|
||||
thisevent.heapposition = smallest;
|
||||
|
||||
eventnum = smallest;
|
||||
leftchild = 2 * eventnum + 1;
|
||||
notdone = leftchild < heapsize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HeapDelete(SweepEvent[] heap, int heapsize, int eventnum)
|
||||
{
|
||||
SweepEvent moveevent;
|
||||
double eventx, eventy;
|
||||
int parent;
|
||||
bool notdone;
|
||||
|
||||
moveevent = heap[heapsize - 1];
|
||||
if (eventnum > 0)
|
||||
{
|
||||
eventx = moveevent.xkey;
|
||||
eventy = moveevent.ykey;
|
||||
do
|
||||
{
|
||||
parent = (eventnum - 1) >> 1;
|
||||
if ((heap[parent].ykey < eventy) ||
|
||||
((heap[parent].ykey == eventy)
|
||||
&& (heap[parent].xkey <= eventx)))
|
||||
{
|
||||
notdone = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
heap[eventnum] = heap[parent];
|
||||
heap[eventnum].heapposition = eventnum;
|
||||
|
||||
eventnum = parent;
|
||||
notdone = eventnum > 0;
|
||||
}
|
||||
} while (notdone);
|
||||
}
|
||||
heap[eventnum] = moveevent;
|
||||
moveevent.heapposition = eventnum;
|
||||
Heapify(heap, heapsize - 1, eventnum);
|
||||
}
|
||||
|
||||
void CreateHeap(out SweepEvent[] eventheap)
|
||||
{
|
||||
Vertex thisvertex;
|
||||
int maxevents;
|
||||
int i;
|
||||
SweepEvent evt;
|
||||
|
||||
maxevents = (3 * mesh.invertices) / 2;
|
||||
eventheap = new SweepEvent[maxevents];
|
||||
|
||||
i = 0;
|
||||
foreach (var v in mesh.vertices.Values)
|
||||
{
|
||||
thisvertex = v;
|
||||
evt = new SweepEvent();
|
||||
evt.vertexEvent = thisvertex;
|
||||
evt.xkey = thisvertex.x;
|
||||
evt.ykey = thisvertex.y;
|
||||
HeapInsert(eventheap, i++, evt);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Splaytree
|
||||
|
||||
SplayNode Splay(SplayNode splaytree, Point searchpoint, ref Otri searchtri)
|
||||
{
|
||||
SplayNode child, grandchild;
|
||||
SplayNode lefttree, righttree;
|
||||
SplayNode leftright;
|
||||
Vertex checkvertex;
|
||||
bool rightofroot, rightofchild;
|
||||
|
||||
if (splaytree == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
checkvertex = splaytree.keyedge.Dest();
|
||||
if (checkvertex == splaytree.keydest)
|
||||
{
|
||||
rightofroot = RightOfHyperbola(ref splaytree.keyedge, searchpoint);
|
||||
if (rightofroot)
|
||||
{
|
||||
splaytree.keyedge.Copy(ref searchtri);
|
||||
child = splaytree.rchild;
|
||||
}
|
||||
else
|
||||
{
|
||||
child = splaytree.lchild;
|
||||
}
|
||||
if (child == null)
|
||||
{
|
||||
return splaytree;
|
||||
}
|
||||
checkvertex = child.keyedge.Dest();
|
||||
if (checkvertex != child.keydest)
|
||||
{
|
||||
child = Splay(child, searchpoint, ref searchtri);
|
||||
if (child == null)
|
||||
{
|
||||
if (rightofroot)
|
||||
{
|
||||
splaytree.rchild = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
splaytree.lchild = null;
|
||||
}
|
||||
return splaytree;
|
||||
}
|
||||
}
|
||||
rightofchild = RightOfHyperbola(ref child.keyedge, searchpoint);
|
||||
if (rightofchild)
|
||||
{
|
||||
child.keyedge.Copy(ref searchtri);
|
||||
grandchild = Splay(child.rchild, searchpoint, ref searchtri);
|
||||
child.rchild = grandchild;
|
||||
}
|
||||
else
|
||||
{
|
||||
grandchild = Splay(child.lchild, searchpoint, ref searchtri);
|
||||
child.lchild = grandchild;
|
||||
}
|
||||
if (grandchild == null)
|
||||
{
|
||||
if (rightofroot)
|
||||
{
|
||||
splaytree.rchild = child.lchild;
|
||||
child.lchild = splaytree;
|
||||
}
|
||||
else
|
||||
{
|
||||
splaytree.lchild = child.rchild;
|
||||
child.rchild = splaytree;
|
||||
}
|
||||
return child;
|
||||
}
|
||||
if (rightofchild)
|
||||
{
|
||||
if (rightofroot)
|
||||
{
|
||||
splaytree.rchild = child.lchild;
|
||||
child.lchild = splaytree;
|
||||
}
|
||||
else
|
||||
{
|
||||
splaytree.lchild = grandchild.rchild;
|
||||
grandchild.rchild = splaytree;
|
||||
}
|
||||
child.rchild = grandchild.lchild;
|
||||
grandchild.lchild = child;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rightofroot)
|
||||
{
|
||||
splaytree.rchild = grandchild.lchild;
|
||||
grandchild.lchild = splaytree;
|
||||
}
|
||||
else
|
||||
{
|
||||
splaytree.lchild = child.rchild;
|
||||
child.rchild = splaytree;
|
||||
}
|
||||
child.lchild = grandchild.rchild;
|
||||
grandchild.rchild = child;
|
||||
}
|
||||
return grandchild;
|
||||
}
|
||||
else
|
||||
{
|
||||
lefttree = Splay(splaytree.lchild, searchpoint, ref searchtri);
|
||||
righttree = Splay(splaytree.rchild, searchpoint, ref searchtri);
|
||||
|
||||
splaynodes.Remove(splaytree);
|
||||
if (lefttree == null)
|
||||
{
|
||||
return righttree;
|
||||
}
|
||||
else if (righttree == null)
|
||||
{
|
||||
return lefttree;
|
||||
}
|
||||
else if (lefttree.rchild == null)
|
||||
{
|
||||
lefttree.rchild = righttree.lchild;
|
||||
righttree.lchild = lefttree;
|
||||
return righttree;
|
||||
}
|
||||
else if (righttree.lchild == null)
|
||||
{
|
||||
righttree.lchild = lefttree.rchild;
|
||||
lefttree.rchild = righttree;
|
||||
return lefttree;
|
||||
}
|
||||
else
|
||||
{
|
||||
// printf("Holy Toledo!!!\n");
|
||||
leftright = lefttree.rchild;
|
||||
while (leftright.rchild != null)
|
||||
{
|
||||
leftright = leftright.rchild;
|
||||
}
|
||||
leftright.rchild = righttree;
|
||||
return lefttree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SplayNode SplayInsert(SplayNode splayroot, Otri newkey, Point searchpoint)
|
||||
{
|
||||
SplayNode newsplaynode;
|
||||
|
||||
newsplaynode = new SplayNode(); //poolalloc(m.splaynodes);
|
||||
splaynodes.Add(newsplaynode);
|
||||
newkey.Copy(ref newsplaynode.keyedge);
|
||||
newsplaynode.keydest = newkey.Dest();
|
||||
if (splayroot == null)
|
||||
{
|
||||
newsplaynode.lchild = null;
|
||||
newsplaynode.rchild = null;
|
||||
}
|
||||
else if (RightOfHyperbola(ref splayroot.keyedge, searchpoint))
|
||||
{
|
||||
newsplaynode.lchild = splayroot;
|
||||
newsplaynode.rchild = splayroot.rchild;
|
||||
splayroot.rchild = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
newsplaynode.lchild = splayroot.lchild;
|
||||
newsplaynode.rchild = splayroot;
|
||||
splayroot.lchild = null;
|
||||
}
|
||||
return newsplaynode;
|
||||
}
|
||||
|
||||
SplayNode FrontLocate(SplayNode splayroot, Otri bottommost, Vertex searchvertex,
|
||||
ref Otri searchtri, ref bool farright)
|
||||
{
|
||||
bool farrightflag;
|
||||
|
||||
bottommost.Copy(ref searchtri);
|
||||
splayroot = Splay(splayroot, searchvertex, ref searchtri);
|
||||
|
||||
farrightflag = false;
|
||||
while (!farrightflag && RightOfHyperbola(ref searchtri, searchvertex))
|
||||
{
|
||||
searchtri.OnextSelf();
|
||||
farrightflag = searchtri.Equal(bottommost);
|
||||
}
|
||||
farright = farrightflag;
|
||||
return splayroot;
|
||||
}
|
||||
|
||||
SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey,
|
||||
Vertex pa, Vertex pb, Vertex pc, double topy)
|
||||
{
|
||||
double ccwabc;
|
||||
double xac, yac, xbc, ybc;
|
||||
double aclen2, bclen2;
|
||||
Point searchpoint = new Point(); // TODO: mesh.nextras
|
||||
Otri dummytri = default(Otri);
|
||||
|
||||
ccwabc = Primitives.CounterClockwise(pa, pb, pc);
|
||||
xac = pa.x - pc.x;
|
||||
yac = pa.y - pc.y;
|
||||
xbc = pb.x - pc.x;
|
||||
ybc = pb.y - pc.y;
|
||||
aclen2 = xac * xac + yac * yac;
|
||||
bclen2 = xbc * xbc + ybc * ybc;
|
||||
searchpoint.x = pc.x - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc);
|
||||
searchpoint.y = topy;
|
||||
return SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
bool RightOfHyperbola(ref Otri fronttri, Point newsite)
|
||||
{
|
||||
Vertex leftvertex, rightvertex;
|
||||
double dxa, dya, dxb, dyb;
|
||||
|
||||
Statistic.HyperbolaCount++;
|
||||
|
||||
leftvertex = fronttri.Dest();
|
||||
rightvertex = fronttri.Apex();
|
||||
if ((leftvertex.y < rightvertex.y) ||
|
||||
((leftvertex.y == rightvertex.y) &&
|
||||
(leftvertex.x < rightvertex.x)))
|
||||
{
|
||||
if (newsite.x >= rightvertex.x)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (newsite.x <= leftvertex.x)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
dxa = leftvertex.x - newsite.x;
|
||||
dya = leftvertex.y - newsite.y;
|
||||
dxb = rightvertex.x - newsite.x;
|
||||
dyb = rightvertex.y - newsite.y;
|
||||
return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya);
|
||||
}
|
||||
|
||||
double CircleTop(Vertex pa, Vertex pb, Vertex pc, double ccwabc)
|
||||
{
|
||||
double xac, yac, xbc, ybc, xab, yab;
|
||||
double aclen2, bclen2, ablen2;
|
||||
|
||||
Statistic.CircleTopCount++;
|
||||
|
||||
xac = pa.x - pc.x;
|
||||
yac = pa.y - pc.y;
|
||||
xbc = pb.x - pc.x;
|
||||
ybc = pb.y - pc.y;
|
||||
xab = pa.x - pb.x;
|
||||
yab = pa.y - pb.y;
|
||||
aclen2 = xac * xac + yac * yac;
|
||||
bclen2 = xbc * xbc + ybc * ybc;
|
||||
ablen2 = xab * xab + yab * yab;
|
||||
return pc.y + (xac * bclen2 - xbc * aclen2 + Math.Sqrt(aclen2 * bclen2 * ablen2)) / (2.0 * ccwabc);
|
||||
}
|
||||
|
||||
void Check4DeadEvent(ref Otri checktri, SweepEvent[] eventheap, ref int heapsize)
|
||||
{
|
||||
SweepEvent deadevent;
|
||||
SweepEventVertex eventvertex;
|
||||
int eventnum = -1;
|
||||
|
||||
eventvertex = checktri.Org() as SweepEventVertex;
|
||||
if (eventvertex != null)
|
||||
{
|
||||
deadevent = eventvertex.evt;
|
||||
eventnum = deadevent.heapposition;
|
||||
|
||||
HeapDelete(eventheap, heapsize, eventnum);
|
||||
heapsize--;
|
||||
checktri.SetOrg(null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes ghost triangles.
|
||||
/// </summary>
|
||||
/// <param name="startghost"></param>
|
||||
/// <returns>Number of vertices on the hull.</returns>
|
||||
int RemoveGhosts(ref Otri startghost)
|
||||
{
|
||||
Otri searchedge = default(Otri);
|
||||
Otri dissolveedge = default(Otri);
|
||||
Otri deadtriangle = default(Otri);
|
||||
Vertex markorg;
|
||||
int hullsize;
|
||||
|
||||
bool noPoly = !mesh.behavior.Poly;
|
||||
|
||||
// Find an edge on the convex hull to start point location from.
|
||||
startghost.Lprev(ref searchedge);
|
||||
searchedge.SymSelf();
|
||||
Mesh.dummytri.neighbors[0] = searchedge;
|
||||
// Remove the bounding box and count the convex hull edges.
|
||||
startghost.Copy(ref dissolveedge);
|
||||
hullsize = 0;
|
||||
do
|
||||
{
|
||||
hullsize++;
|
||||
dissolveedge.Lnext(ref deadtriangle);
|
||||
dissolveedge.LprevSelf();
|
||||
dissolveedge.SymSelf();
|
||||
|
||||
// If no PSLG is involved, set the boundary markers of all the vertices
|
||||
// on the convex hull. If a PSLG is used, this step is done later.
|
||||
if (noPoly)
|
||||
{
|
||||
// Watch out for the case where all the input vertices are collinear.
|
||||
if (dissolveedge.triangle != Mesh.dummytri)
|
||||
{
|
||||
markorg = dissolveedge.Org();
|
||||
if (markorg.mark == 0)
|
||||
{
|
||||
markorg.mark = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove a bounding triangle from a convex hull triangle.
|
||||
dissolveedge.Dissolve();
|
||||
// Find the next bounding triangle.
|
||||
deadtriangle.Sym(ref dissolveedge);
|
||||
|
||||
// Delete the bounding triangle.
|
||||
mesh.TriangleDealloc(deadtriangle.triangle);
|
||||
} while (!dissolveedge.Equal(startghost));
|
||||
|
||||
return hullsize;
|
||||
}
|
||||
|
||||
public int Triangulate(Mesh mesh)
|
||||
{
|
||||
this.mesh = mesh;
|
||||
|
||||
// Nonexistent x value used as a flag to mark circle events in sweepline
|
||||
// Delaunay algorithm.
|
||||
xminextreme = 10 * mesh.bounds.Left - 9 * mesh.bounds.Right;
|
||||
|
||||
SweepEvent[] eventheap;
|
||||
|
||||
SweepEvent nextevent;
|
||||
SweepEvent newevent;
|
||||
SplayNode splayroot;
|
||||
Otri bottommost = default(Otri);
|
||||
Otri searchtri = default(Otri);
|
||||
Otri fliptri;
|
||||
Otri lefttri = default(Otri);
|
||||
Otri righttri = default(Otri);
|
||||
Otri farlefttri = default(Otri);
|
||||
Otri farrighttri = default(Otri);
|
||||
Otri inserttri = default(Otri);
|
||||
Vertex firstvertex, secondvertex;
|
||||
Vertex nextvertex, lastvertex;
|
||||
Vertex connectvertex;
|
||||
Vertex leftvertex, midvertex, rightvertex;
|
||||
double lefttest, righttest;
|
||||
int heapsize;
|
||||
bool check4events, farrightflag = false;
|
||||
|
||||
splaynodes = new List<SplayNode>();
|
||||
splayroot = null;
|
||||
|
||||
CreateHeap(out eventheap);//, out events, out freeevents);
|
||||
heapsize = mesh.invertices;
|
||||
|
||||
mesh.MakeTriangle(ref lefttri);
|
||||
mesh.MakeTriangle(ref righttri);
|
||||
lefttri.Bond(ref righttri);
|
||||
lefttri.LnextSelf();
|
||||
righttri.LprevSelf();
|
||||
lefttri.Bond(ref righttri);
|
||||
lefttri.LnextSelf();
|
||||
righttri.LprevSelf();
|
||||
lefttri.Bond(ref righttri);
|
||||
firstvertex = eventheap[0].vertexEvent;
|
||||
|
||||
HeapDelete(eventheap, heapsize, 0);
|
||||
heapsize--;
|
||||
do
|
||||
{
|
||||
if (heapsize == 0)
|
||||
{
|
||||
SimpleLog.Instance.Error("Input vertices are all identical.", "SweepLine.Triangulate()");
|
||||
throw new Exception("Input vertices are all identical.");
|
||||
}
|
||||
secondvertex = eventheap[0].vertexEvent;
|
||||
HeapDelete(eventheap, heapsize, 0);
|
||||
heapsize--;
|
||||
if ((firstvertex.x == secondvertex.x) &&
|
||||
(firstvertex.y == secondvertex.y))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
SimpleLog.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + secondvertex.id + ").",
|
||||
"SweepLine.Triangulate().1");
|
||||
}
|
||||
secondvertex.type = VertexType.UndeadVertex;
|
||||
mesh.undeads++;
|
||||
}
|
||||
} while ((firstvertex.x == secondvertex.x) &&
|
||||
(firstvertex.y == secondvertex.y));
|
||||
lefttri.SetOrg(firstvertex);
|
||||
lefttri.SetDest(secondvertex);
|
||||
righttri.SetOrg(secondvertex);
|
||||
righttri.SetDest(firstvertex);
|
||||
lefttri.Lprev(ref bottommost);
|
||||
lastvertex = secondvertex;
|
||||
|
||||
while (heapsize > 0)
|
||||
{
|
||||
nextevent = eventheap[0];
|
||||
HeapDelete(eventheap, heapsize, 0);
|
||||
heapsize--;
|
||||
check4events = true;
|
||||
if (nextevent.xkey < mesh.bounds.Left)
|
||||
{
|
||||
fliptri = nextevent.otriEvent;
|
||||
fliptri.Oprev(ref farlefttri);
|
||||
Check4DeadEvent(ref farlefttri, eventheap, ref heapsize);
|
||||
fliptri.Onext(ref farrighttri);
|
||||
Check4DeadEvent(ref farrighttri, eventheap, ref heapsize);
|
||||
|
||||
if (farlefttri.Equal(bottommost))
|
||||
{
|
||||
fliptri.Lprev(ref bottommost);
|
||||
}
|
||||
mesh.Flip(ref fliptri);
|
||||
fliptri.SetApex(null);
|
||||
fliptri.Lprev(ref lefttri);
|
||||
fliptri.Lnext(ref righttri);
|
||||
lefttri.Sym(ref farlefttri);
|
||||
|
||||
if (randomnation(SAMPLERATE) == 0)
|
||||
{
|
||||
fliptri.SymSelf();
|
||||
leftvertex = fliptri.Dest();
|
||||
midvertex = fliptri.Apex();
|
||||
rightvertex = fliptri.Org();
|
||||
splayroot = CircleTopInsert(splayroot, lefttri, leftvertex, midvertex, rightvertex, nextevent.ykey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nextvertex = nextevent.vertexEvent;
|
||||
if ((nextvertex.x == lastvertex.x) &&
|
||||
(nextvertex.y == lastvertex.y))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
SimpleLog.Instance.Warning("A duplicate vertex appeared and was ignored (ID " + nextvertex.id + ").",
|
||||
"SweepLine.Triangulate().2");
|
||||
}
|
||||
nextvertex.type = VertexType.UndeadVertex;
|
||||
mesh.undeads++;
|
||||
check4events = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastvertex = nextvertex;
|
||||
|
||||
splayroot = FrontLocate(splayroot, bottommost, nextvertex, ref searchtri, ref farrightflag);
|
||||
|
||||
//bottommost.Copy(ref searchtri);
|
||||
//farrightflag = false;
|
||||
//while (!farrightflag && RightOfHyperbola(ref searchtri, nextvertex))
|
||||
//{
|
||||
// searchtri.OnextSelf();
|
||||
// farrightflag = searchtri.Equal(bottommost);
|
||||
//}
|
||||
|
||||
Check4DeadEvent(ref searchtri, eventheap, ref heapsize);
|
||||
|
||||
searchtri.Copy(ref farrighttri);
|
||||
searchtri.Sym(ref farlefttri);
|
||||
mesh.MakeTriangle(ref lefttri);
|
||||
mesh.MakeTriangle(ref righttri);
|
||||
connectvertex = farrighttri.Dest();
|
||||
lefttri.SetOrg(connectvertex);
|
||||
lefttri.SetDest(nextvertex);
|
||||
righttri.SetOrg(nextvertex);
|
||||
righttri.SetDest(connectvertex);
|
||||
lefttri.Bond(ref righttri);
|
||||
lefttri.LnextSelf();
|
||||
righttri.LprevSelf();
|
||||
lefttri.Bond(ref righttri);
|
||||
lefttri.LnextSelf();
|
||||
righttri.LprevSelf();
|
||||
lefttri.Bond(ref farlefttri);
|
||||
righttri.Bond(ref farrighttri);
|
||||
if (!farrightflag && farrighttri.Equal(bottommost))
|
||||
{
|
||||
lefttri.Copy(ref bottommost);
|
||||
}
|
||||
|
||||
if (randomnation(SAMPLERATE) == 0)
|
||||
{
|
||||
splayroot = SplayInsert(splayroot, lefttri, nextvertex);
|
||||
}
|
||||
else if (randomnation(SAMPLERATE) == 0)
|
||||
{
|
||||
righttri.Lnext(ref inserttri);
|
||||
splayroot = SplayInsert(splayroot, inserttri, nextvertex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (check4events)
|
||||
{
|
||||
leftvertex = farlefttri.Apex();
|
||||
midvertex = lefttri.Dest();
|
||||
rightvertex = lefttri.Apex();
|
||||
lefttest = Primitives.CounterClockwise(leftvertex, midvertex, rightvertex);
|
||||
if (lefttest > 0.0)
|
||||
{
|
||||
newevent = new SweepEvent();
|
||||
|
||||
newevent.xkey = xminextreme;
|
||||
newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, lefttest);
|
||||
newevent.otriEvent = lefttri;
|
||||
HeapInsert(eventheap, heapsize, newevent);
|
||||
heapsize++;
|
||||
lefttri.SetOrg(new SweepEventVertex(newevent));
|
||||
}
|
||||
leftvertex = righttri.Apex();
|
||||
midvertex = righttri.Org();
|
||||
rightvertex = farrighttri.Apex();
|
||||
righttest = Primitives.CounterClockwise(leftvertex, midvertex, rightvertex);
|
||||
if (righttest > 0.0)
|
||||
{
|
||||
newevent = new SweepEvent();
|
||||
|
||||
newevent.xkey = xminextreme;
|
||||
newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, righttest);
|
||||
newevent.otriEvent = farrighttri;
|
||||
HeapInsert(eventheap, heapsize, newevent);
|
||||
heapsize++;
|
||||
farrighttri.SetOrg(new SweepEventVertex(newevent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
splaynodes.Clear();
|
||||
bottommost.LprevSelf();
|
||||
return RemoveGhosts(ref bottommost);
|
||||
}
|
||||
|
||||
#region Internal classes
|
||||
|
||||
/// <summary>
|
||||
/// A node in a heap used to store events for the sweepline Delaunay algorithm.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used in the sweepline algorithm.
|
||||
///
|
||||
/// Nodes do not point directly to their parents or children in the heap. Instead, each
|
||||
/// node knows its position in the heap, and can look up its parent and children in a
|
||||
/// separate array. To distinguish site events from circle events, all circle events are
|
||||
/// given an invalid (smaller than 'xmin') x-coordinate 'xkey'.
|
||||
/// </remarks>
|
||||
class SweepEvent
|
||||
{
|
||||
public double xkey, ykey; // Coordinates of the event.
|
||||
public Vertex vertexEvent; // Vertex event.
|
||||
public Otri otriEvent; // Circle event.
|
||||
public int heapposition; // Marks this event's position in the heap.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Introducing a new class which aggregates a sweep event is the easiest way
|
||||
/// to handle the pointer magic of the original code (casting a sweep event
|
||||
/// to vertex etc.).
|
||||
/// </summary>
|
||||
class SweepEventVertex : Vertex
|
||||
{
|
||||
public SweepEvent evt;
|
||||
|
||||
public SweepEventVertex(SweepEvent e)
|
||||
{
|
||||
evt = e;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A node in the splay tree.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only used in the sweepline algorithm.
|
||||
///
|
||||
/// Each node holds an oriented ghost triangle that represents a boundary edge
|
||||
/// of the growing triangulation. When a circle event covers two boundary edges
|
||||
/// with a triangle, so that they are no longer boundary edges, those edges are
|
||||
/// not immediately deleted from the tree; rather, they are lazily deleted when
|
||||
/// they are next encountered. (Since only a random sample of boundary edges are
|
||||
/// kept in the tree, lazy deletion is faster.) 'keydest' is used to verify that
|
||||
/// a triangle is still the same as when it entered the splay tree; if it has
|
||||
/// been rotated (due to a circle event), it no longer represents a boundary
|
||||
/// edge and should be deleted.
|
||||
/// </remarks>
|
||||
class SplayNode
|
||||
{
|
||||
public Otri keyedge; // Lprev of an edge on the front.
|
||||
public Vertex keydest; // Used to verify that splay node is still live.
|
||||
public SplayNode lchild, rchild; // Children in splay tree.
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
|
||||
namespace TriangleNet.Meshing
|
||||
{
|
||||
public class ConstraintOptions
|
||||
{
|
||||
public static ConstraintOptions Empty
|
||||
{
|
||||
get { return new ConstraintOptions(); }
|
||||
}
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating wether to use regions.
|
||||
/// </summary>
|
||||
public bool UseRegions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating wether to create a Conforming
|
||||
/// Delaunay triangulation.
|
||||
/// </summary>
|
||||
public bool ConformingDelaunay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enclose the convex hull with segments.
|
||||
/// </summary>
|
||||
public bool Convex { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Converter.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.Meshing
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TriangleNet.Data;
|
||||
using TriangleNet.Geometry;
|
||||
using TriangleNet.Log;
|
||||
|
||||
/// <summary>
|
||||
/// The DataReader class provides methods for mesh reconstruction.
|
||||
/// </summary>
|
||||
public class Converter
|
||||
{
|
||||
public Mesh ToMesh(InputGeometry polygon, IList<ITriangle> triangles)
|
||||
{
|
||||
return ToMesh(polygon, triangles.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reconstruct a triangulation from its raw data representation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reads an .ele file and reconstructs the original mesh. If the -p switch
|
||||
/// is used, this procedure will also read a .poly file and reconstruct the
|
||||
/// subsegments of the original mesh. If the -a switch is used, this
|
||||
/// procedure will also read an .area file and set a maximum area constraint
|
||||
/// on each triangle.
|
||||
///
|
||||
/// Vertices that are not corners of triangles, such as nodes on edges of
|
||||
/// subparametric elements, are discarded.
|
||||
///
|
||||
/// This routine finds the adjacencies between triangles (and subsegments)
|
||||
/// by forming one stack of triangles for each vertex. Each triangle is on
|
||||
/// three different stacks simultaneously. Each triangle's subsegment
|
||||
/// pointers are used to link the items in each stack. This memory-saving
|
||||
/// feature makes the code harder to read. The most important thing to keep
|
||||
/// in mind is that each triangle is removed from a stack precisely when
|
||||
/// the corresponding pointer is adjusted to refer to a subsegment rather
|
||||
/// than the next triangle of the stack.
|
||||
/// </remarks>
|
||||
public Mesh ToMesh(InputGeometry polygon, ITriangle[] triangles)
|
||||
{
|
||||
Otri tri = default(Otri);
|
||||
Osub subseg = default(Osub);
|
||||
int i = 0;
|
||||
|
||||
int elements = triangles == null ? 0 : triangles.Length;
|
||||
int numberofsegments = polygon.Segments.Count;
|
||||
|
||||
var mesh = new Mesh();
|
||||
|
||||
mesh.TransferNodes(polygon);
|
||||
|
||||
mesh.inelements = elements;
|
||||
mesh.regions.AddRange(polygon.Regions);
|
||||
mesh.behavior.useRegions = polygon.Regions.Count > 0;
|
||||
|
||||
if (polygon.Segments.Count > 0)
|
||||
{
|
||||
mesh.behavior.Poly = true;
|
||||
mesh.holes.AddRange(polygon.Holes);
|
||||
}
|
||||
|
||||
// Create the triangles.
|
||||
for (i = 0; i < mesh.inelements; i++)
|
||||
{
|
||||
mesh.MakeTriangle(ref tri);
|
||||
}
|
||||
|
||||
if (mesh.behavior.Poly)
|
||||
{
|
||||
mesh.insegments = numberofsegments;
|
||||
|
||||
// Create the subsegments.
|
||||
for (i = 0; i < mesh.insegments; i++)
|
||||
{
|
||||
mesh.MakeSegment(ref subseg);
|
||||
}
|
||||
}
|
||||
|
||||
var vertexarray = SetNeighbors(mesh, triangles);
|
||||
|
||||
SetSegments(mesh, polygon, vertexarray);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the adjacencies between triangles by forming a stack of triangles
|
||||
/// for each vertex.
|
||||
/// </summary>
|
||||
private static List<Otri>[] SetNeighbors(Mesh mesh, ITriangle[] triangles)
|
||||
{
|
||||
Otri tri = default(Otri);
|
||||
Otri triangleleft = default(Otri);
|
||||
Otri checktri = default(Otri);
|
||||
Otri checkleft = default(Otri);
|
||||
Otri nexttri; // Triangle
|
||||
Vertex tdest, tapex;
|
||||
Vertex checkdest, checkapex;
|
||||
int[] corner = new int[3];
|
||||
int aroundvertex;
|
||||
int i;
|
||||
|
||||
// Allocate a temporary array that maps each vertex to some adjacent
|
||||
// triangle.
|
||||
var vertexarray = new List<Otri>[mesh.vertices.Count];
|
||||
|
||||
// Each vertex is initially unrepresented.
|
||||
for (i = 0; i < mesh.vertices.Count; i++)
|
||||
{
|
||||
Otri tmp = default(Otri);
|
||||
tmp.triangle = Mesh.dummytri;
|
||||
vertexarray[i] = new List<Otri>(3);
|
||||
vertexarray[i].Add(tmp);
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
// Read the triangles from the .ele file, and link
|
||||
// together those that share an edge.
|
||||
foreach (var item in mesh.triangles.Values)
|
||||
{
|
||||
tri.triangle = item;
|
||||
|
||||
corner[0] = triangles[i].P0;
|
||||
corner[1] = triangles[i].P1;
|
||||
corner[2] = triangles[i].P2;
|
||||
|
||||
// Copy the triangle's three corners.
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
if ((corner[j] < 0) || (corner[j] >= mesh.invertices))
|
||||
{
|
||||
SimpleLog.Instance.Error("Triangle has an invalid vertex index.", "MeshReader.Reconstruct()");
|
||||
throw new Exception("Triangle has an invalid vertex index.");
|
||||
}
|
||||
}
|
||||
|
||||
// Read the triangle's attributes.
|
||||
tri.triangle.region = triangles[i].Region;
|
||||
|
||||
// TODO: VarArea
|
||||
if (mesh.behavior.VarArea)
|
||||
{
|
||||
tri.triangle.area = triangles[i].Area;
|
||||
}
|
||||
|
||||
// Set the triangle's vertices.
|
||||
tri.orient = 0;
|
||||
tri.SetOrg(mesh.vertices[corner[0]]);
|
||||
tri.SetDest(mesh.vertices[corner[1]]);
|
||||
tri.SetApex(mesh.vertices[corner[2]]);
|
||||
|
||||
// Try linking the triangle to others that share these vertices.
|
||||
for (tri.orient = 0; tri.orient < 3; tri.orient++)
|
||||
{
|
||||
// Take the number for the origin of triangleloop.
|
||||
aroundvertex = corner[tri.orient];
|
||||
int index = vertexarray[aroundvertex].Count - 1;
|
||||
// Look for other triangles having this vertex.
|
||||
nexttri = vertexarray[aroundvertex][index];
|
||||
// Link the current triangle to the next one in the stack.
|
||||
//tri.triangle.neighbors[tri.orient] = nexttri;
|
||||
// Push the current triangle onto the stack.
|
||||
vertexarray[aroundvertex].Add(tri);
|
||||
|
||||
checktri = nexttri;
|
||||
|
||||
if (checktri.triangle != Mesh.dummytri)
|
||||
{
|
||||
tdest = tri.Dest();
|
||||
tapex = tri.Apex();
|
||||
|
||||
// Look for other triangles that share an edge.
|
||||
do
|
||||
{
|
||||
checkdest = checktri.Dest();
|
||||
checkapex = checktri.Apex();
|
||||
|
||||
if (tapex == checkdest)
|
||||
{
|
||||
// The two triangles share an edge; bond them together.
|
||||
tri.Lprev(ref triangleleft);
|
||||
triangleleft.Bond(ref checktri);
|
||||
}
|
||||
if (tdest == checkapex)
|
||||
{
|
||||
// The two triangles share an edge; bond them together.
|
||||
checktri.Lprev(ref checkleft);
|
||||
tri.Bond(ref checkleft);
|
||||
}
|
||||
// Find the next triangle in the stack.
|
||||
index--;
|
||||
nexttri = vertexarray[aroundvertex][index];
|
||||
|
||||
checktri = nexttri;
|
||||
} while (checktri.triangle != Mesh.dummytri);
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return vertexarray;
|
||||
}
|
||||
|
||||
private static void SetSegments(Mesh mesh, InputGeometry polygon, List<Otri>[] vertexarray)
|
||||
{
|
||||
Otri checktri = default(Otri);
|
||||
Otri nexttri; // Triangle
|
||||
Vertex checkdest;
|
||||
Otri checkneighbor = default(Otri);
|
||||
Osub subseg = default(Osub);
|
||||
Otri prevlink; // Triangle
|
||||
Vertex shorg;
|
||||
Vertex segmentorg, segmentdest;
|
||||
int[] end = new int[2];
|
||||
bool notfound;
|
||||
//bool segmentmarkers = false;
|
||||
int boundmarker;
|
||||
int aroundvertex;
|
||||
int i;
|
||||
|
||||
int hullsize = 0;
|
||||
|
||||
// Prepare to count the boundary edges.
|
||||
if (mesh.behavior.Poly)
|
||||
{
|
||||
// Link the segments to their neighboring triangles.
|
||||
boundmarker = 0;
|
||||
i = 0;
|
||||
foreach (var item in mesh.subsegs.Values)
|
||||
{
|
||||
subseg.seg = item;
|
||||
|
||||
end[0] = polygon.segments[i].P0;
|
||||
end[1] = polygon.segments[i].P1;
|
||||
boundmarker = polygon.segments[i].Boundary;
|
||||
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
if ((end[j] < 0) || (end[j] >= mesh.invertices))
|
||||
{
|
||||
SimpleLog.Instance.Error("Segment has an invalid vertex index.", "MeshReader.Reconstruct()");
|
||||
throw new Exception("Segment has an invalid vertex index.");
|
||||
}
|
||||
}
|
||||
|
||||
// set the subsegment's vertices.
|
||||
subseg.orient = 0;
|
||||
segmentorg = mesh.vertices[end[0]];
|
||||
segmentdest = mesh.vertices[end[1]];
|
||||
subseg.SetOrg(segmentorg);
|
||||
subseg.SetDest(segmentdest);
|
||||
subseg.SetSegOrg(segmentorg);
|
||||
subseg.SetSegDest(segmentdest);
|
||||
subseg.seg.boundary = boundmarker;
|
||||
// Try linking the subsegment to triangles that share these vertices.
|
||||
for (subseg.orient = 0; subseg.orient < 2; subseg.orient++)
|
||||
{
|
||||
// Take the number for the destination of subsegloop.
|
||||
aroundvertex = end[1 - subseg.orient];
|
||||
int index = vertexarray[aroundvertex].Count - 1;
|
||||
// Look for triangles having this vertex.
|
||||
prevlink = vertexarray[aroundvertex][index];
|
||||
nexttri = vertexarray[aroundvertex][index];
|
||||
|
||||
checktri = nexttri;
|
||||
shorg = subseg.Org();
|
||||
notfound = true;
|
||||
// Look for triangles having this edge. Note that I'm only
|
||||
// comparing each triangle's destination with the subsegment;
|
||||
// each triangle's apex is handled through a different vertex.
|
||||
// Because each triangle appears on three vertices' lists, each
|
||||
// occurrence of a triangle on a list can (and does) represent
|
||||
// an edge. In this way, most edges are represented twice, and
|
||||
// every triangle-subsegment bond is represented once.
|
||||
while (notfound && (checktri.triangle != Mesh.dummytri))
|
||||
{
|
||||
checkdest = checktri.Dest();
|
||||
|
||||
if (shorg == checkdest)
|
||||
{
|
||||
// We have a match. Remove this triangle from the list.
|
||||
//prevlink = vertexarray[aroundvertex][index];
|
||||
vertexarray[aroundvertex].Remove(prevlink);
|
||||
// Bond the subsegment to the triangle.
|
||||
checktri.SegBond(ref subseg);
|
||||
// Check if this is a boundary edge.
|
||||
checktri.Sym(ref checkneighbor);
|
||||
if (checkneighbor.triangle == Mesh.dummytri)
|
||||
{
|
||||
// The next line doesn't insert a subsegment (because there's
|
||||
// already one there), but it sets the boundary markers of
|
||||
// the existing subsegment and its vertices.
|
||||
mesh.InsertSubseg(ref checktri, 1);
|
||||
hullsize++;
|
||||
}
|
||||
notfound = false;
|
||||
}
|
||||
index--;
|
||||
// Find the next triangle in the stack.
|
||||
prevlink = vertexarray[aroundvertex][index];
|
||||
nexttri = vertexarray[aroundvertex][index];
|
||||
|
||||
checktri = nexttri;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the remaining edges as not being attached to any subsegment.
|
||||
// Also, count the (yet uncounted) boundary edges.
|
||||
for (i = 0; i < mesh.vertices.Count; i++)
|
||||
{
|
||||
// Search the stack of triangles adjacent to a vertex.
|
||||
int index = vertexarray[i].Count - 1;
|
||||
nexttri = vertexarray[i][index];
|
||||
checktri = nexttri;
|
||||
|
||||
while (checktri.triangle != Mesh.dummytri)
|
||||
{
|
||||
// Find the next triangle in the stack before this
|
||||
// information gets overwritten.
|
||||
index--;
|
||||
nexttri = vertexarray[i][index];
|
||||
// No adjacent subsegment. (This overwrites the stack info.)
|
||||
checktri.SegDissolve();
|
||||
checktri.Sym(ref checkneighbor);
|
||||
if (checkneighbor.triangle == Mesh.dummytri)
|
||||
{
|
||||
mesh.InsertSubseg(ref checktri, 1);
|
||||
hullsize++;
|
||||
}
|
||||
|
||||
checktri = nexttri;
|
||||
}
|
||||
}
|
||||
|
||||
mesh.hullsize = hullsize;
|
||||
mesh.edges = (3 * mesh.triangles.Count + hullsize) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
namespace TriangleNet.Meshing
|
||||
{
|
||||
using TriangleNet.Geometry;
|
||||
|
||||
public interface IConstraintMesher
|
||||
{
|
||||
Mesh Triangulate(IPolygon polygon);
|
||||
Mesh Triangulate(IPolygon polygon, ConstraintOptions options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
namespace TriangleNet.Meshing
|
||||
{
|
||||
using TriangleNet.Geometry;
|
||||
|
||||
public interface IQualityMesher
|
||||
{
|
||||
Mesh Triangulate(IPolygon polygon, QualityOptions quality);
|
||||
Mesh Triangulate(IPolygon polygon, ConstraintOptions options, QualityOptions quality);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="ITriangulator.cs" company="">
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace TriangleNet.Meshing
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Update summary.
|
||||
/// </summary>
|
||||
public interface ITriangulator
|
||||
{
|
||||
int Triangulate(Mesh mesh);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,826 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Quality.cs">
|
||||
// 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.Meshing
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TriangleNet.Data;
|
||||
using TriangleNet.Log;
|
||||
using TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods for mesh quality enforcement and testing.
|
||||
/// </summary>
|
||||
class QualityMesher
|
||||
{
|
||||
Queue<BadSubseg> badsubsegs;
|
||||
BadTriQueue queue;
|
||||
Mesh mesh;
|
||||
Behavior behavior;
|
||||
|
||||
NewLocation newLocation;
|
||||
|
||||
ILog<SimpleLogItem> logger;
|
||||
|
||||
public QualityMesher(Mesh mesh)
|
||||
{
|
||||
logger = SimpleLog.Instance;
|
||||
|
||||
badsubsegs = new Queue<BadSubseg>();
|
||||
queue = new BadTriQueue();
|
||||
|
||||
this.mesh = mesh;
|
||||
this.behavior = mesh.behavior;
|
||||
|
||||
newLocation = new NewLocation(mesh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a bad subsegment to the queue.
|
||||
/// </summary>
|
||||
/// <param name="badseg">Bad subsegment.</param>
|
||||
public void AddBadSubseg(BadSubseg badseg)
|
||||
{
|
||||
badsubsegs.Enqueue(badseg);
|
||||
}
|
||||
|
||||
#region Check
|
||||
|
||||
/// <summary>
|
||||
/// Check a subsegment to see if it is encroached; add it to the list if it is.
|
||||
/// </summary>
|
||||
/// <param name="testsubseg">The subsegment to check.</param>
|
||||
/// <returns>Returns a nonzero value if the subsegment is encroached.</returns>
|
||||
/// <remarks>
|
||||
/// A subsegment is encroached if there is a vertex in its diametral lens.
|
||||
/// For Ruppert's algorithm (-D switch), the "diametral lens" is the
|
||||
/// diametral circle. For Chew's algorithm (default), the diametral lens is
|
||||
/// just big enough to enclose two isosceles triangles whose bases are the
|
||||
/// subsegment. Each of the two isosceles triangles has two angles equal
|
||||
/// to 'b.minangle'.
|
||||
///
|
||||
/// Chew's algorithm does not require diametral lenses at all--but they save
|
||||
/// time. Any vertex inside a subsegment's diametral lens implies that the
|
||||
/// triangle adjoining the subsegment will be too skinny, so it's only a
|
||||
/// matter of time before the encroaching vertex is deleted by Chew's
|
||||
/// algorithm. It's faster to simply not insert the doomed vertex in the
|
||||
/// first place, which is why I use diametral lenses with Chew's algorithm.
|
||||
/// </remarks>
|
||||
public int CheckSeg4Encroach(ref Osub testsubseg)
|
||||
{
|
||||
Otri neighbortri = default(Otri);
|
||||
Osub testsym = default(Osub);
|
||||
BadSubseg encroachedseg;
|
||||
double dotproduct;
|
||||
int encroached;
|
||||
int sides;
|
||||
Vertex eorg, edest, eapex;
|
||||
|
||||
encroached = 0;
|
||||
sides = 0;
|
||||
|
||||
eorg = testsubseg.Org();
|
||||
edest = testsubseg.Dest();
|
||||
// Check one neighbor of the subsegment.
|
||||
testsubseg.TriPivot(ref neighbortri);
|
||||
// Does the neighbor exist, or is this a boundary edge?
|
||||
if (neighbortri.triangle != Mesh.dummytri)
|
||||
{
|
||||
sides++;
|
||||
// Find a vertex opposite this subsegment.
|
||||
eapex = neighbortri.Apex();
|
||||
// Check whether the apex is in the diametral lens of the subsegment
|
||||
// (the diametral circle if 'conformdel' is set). A dot product
|
||||
// of two sides of the triangle is used to check whether the angle
|
||||
// at the apex is greater than (180 - 2 'minangle') degrees (for
|
||||
// lenses; 90 degrees for diametral circles).
|
||||
dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) +
|
||||
(eorg.y - eapex.y) * (edest.y - eapex.y);
|
||||
if (dotproduct < 0.0)
|
||||
{
|
||||
if (behavior.ConformingDelaunay ||
|
||||
(dotproduct * dotproduct >=
|
||||
(2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) *
|
||||
((eorg.x - eapex.x) * (eorg.x - eapex.x) +
|
||||
(eorg.y - eapex.y) * (eorg.y - eapex.y)) *
|
||||
((edest.x - eapex.x) * (edest.x - eapex.x) +
|
||||
(edest.y - eapex.y) * (edest.y - eapex.y))))
|
||||
{
|
||||
encroached = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check the other neighbor of the subsegment.
|
||||
testsubseg.Sym(ref testsym);
|
||||
testsym.TriPivot(ref neighbortri);
|
||||
// Does the neighbor exist, or is this a boundary edge?
|
||||
if (neighbortri.triangle != Mesh.dummytri)
|
||||
{
|
||||
sides++;
|
||||
// Find the other vertex opposite this subsegment.
|
||||
eapex = neighbortri.Apex();
|
||||
// Check whether the apex is in the diametral lens of the subsegment
|
||||
// (or the diametral circle, if 'conformdel' is set).
|
||||
dotproduct = (eorg.x - eapex.x) * (edest.x - eapex.x) +
|
||||
(eorg.y - eapex.y) * (edest.y - eapex.y);
|
||||
if (dotproduct < 0.0)
|
||||
{
|
||||
if (behavior.ConformingDelaunay ||
|
||||
(dotproduct * dotproduct >=
|
||||
(2.0 * behavior.goodAngle - 1.0) * (2.0 * behavior.goodAngle - 1.0) *
|
||||
((eorg.x - eapex.x) * (eorg.x - eapex.x) +
|
||||
(eorg.y - eapex.y) * (eorg.y - eapex.y)) *
|
||||
((edest.x - eapex.x) * (edest.x - eapex.x) +
|
||||
(edest.y - eapex.y) * (edest.y - eapex.y))))
|
||||
{
|
||||
encroached += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (encroached > 0 && (behavior.NoBisect == 0 || ((behavior.NoBisect == 1) && (sides == 2))))
|
||||
{
|
||||
// Add the subsegment to the list of encroached subsegments.
|
||||
// Be sure to get the orientation right.
|
||||
encroachedseg = new BadSubseg();
|
||||
if (encroached == 1)
|
||||
{
|
||||
encroachedseg.encsubseg = testsubseg;
|
||||
encroachedseg.subsegorg = eorg;
|
||||
encroachedseg.subsegdest = edest;
|
||||
}
|
||||
else
|
||||
{
|
||||
encroachedseg.encsubseg = testsym;
|
||||
encroachedseg.subsegorg = edest;
|
||||
encroachedseg.subsegdest = eorg;
|
||||
}
|
||||
|
||||
badsubsegs.Enqueue(encroachedseg);
|
||||
}
|
||||
|
||||
return encroached;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test a triangle for quality and size.
|
||||
/// </summary>
|
||||
/// <param name="testtri">Triangle to check.</param>
|
||||
/// <remarks>
|
||||
/// Tests a triangle to see if it satisfies the minimum angle condition and
|
||||
/// the maximum area condition. Triangles that aren't up to spec are added
|
||||
/// to the bad triangle queue.
|
||||
/// </remarks>
|
||||
public void TestTriangle(ref Otri testtri)
|
||||
{
|
||||
Otri tri1 = default(Otri), tri2 = default(Otri);
|
||||
Osub testsub = default(Osub);
|
||||
Vertex torg, tdest, tapex;
|
||||
Vertex base1, base2;
|
||||
Vertex org1, dest1, org2, dest2;
|
||||
Vertex joinvertex;
|
||||
double dxod, dyod, dxda, dyda, dxao, dyao;
|
||||
double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2;
|
||||
double apexlen, orglen, destlen, minedge;
|
||||
double angle;
|
||||
double area;
|
||||
double dist1, dist2;
|
||||
|
||||
double maxangle;
|
||||
|
||||
torg = testtri.Org();
|
||||
tdest = testtri.Dest();
|
||||
tapex = testtri.Apex();
|
||||
dxod = torg.x - tdest.x;
|
||||
dyod = torg.y - tdest.y;
|
||||
dxda = tdest.x - tapex.x;
|
||||
dyda = tdest.y - tapex.y;
|
||||
dxao = tapex.x - torg.x;
|
||||
dyao = tapex.y - torg.y;
|
||||
dxod2 = dxod * dxod;
|
||||
dyod2 = dyod * dyod;
|
||||
dxda2 = dxda * dxda;
|
||||
dyda2 = dyda * dyda;
|
||||
dxao2 = dxao * dxao;
|
||||
dyao2 = dyao * dyao;
|
||||
// Find the lengths of the triangle's three edges.
|
||||
apexlen = dxod2 + dyod2;
|
||||
orglen = dxda2 + dyda2;
|
||||
destlen = dxao2 + dyao2;
|
||||
|
||||
if ((apexlen < orglen) && (apexlen < destlen))
|
||||
{
|
||||
// The edge opposite the apex is shortest.
|
||||
minedge = apexlen;
|
||||
// Find the square of the cosine of the angle at the apex.
|
||||
angle = dxda * dxao + dyda * dyao;
|
||||
angle = angle * angle / (orglen * destlen);
|
||||
base1 = torg;
|
||||
base2 = tdest;
|
||||
testtri.Copy(ref tri1);
|
||||
}
|
||||
else if (orglen < destlen)
|
||||
{
|
||||
// The edge opposite the origin is shortest.
|
||||
minedge = orglen;
|
||||
// Find the square of the cosine of the angle at the origin.
|
||||
angle = dxod * dxao + dyod * dyao;
|
||||
angle = angle * angle / (apexlen * destlen);
|
||||
base1 = tdest;
|
||||
base2 = tapex;
|
||||
testtri.Lnext(ref tri1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The edge opposite the destination is shortest.
|
||||
minedge = destlen;
|
||||
// Find the square of the cosine of the angle at the destination.
|
||||
angle = dxod * dxda + dyod * dyda;
|
||||
angle = angle * angle / (apexlen * orglen);
|
||||
base1 = tapex;
|
||||
base2 = torg;
|
||||
testtri.Lprev(ref tri1);
|
||||
}
|
||||
|
||||
if (behavior.VarArea || behavior.fixedArea || (behavior.UserTest != null))
|
||||
{
|
||||
// Check whether the area is larger than permitted.
|
||||
area = 0.5 * (dxod * dyda - dyod * dxda);
|
||||
if (behavior.fixedArea && (area > behavior.MaxArea))
|
||||
{
|
||||
// Add this triangle to the list of bad triangles.
|
||||
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
|
||||
return;
|
||||
}
|
||||
|
||||
// Nonpositive area constraints are treated as unconstrained.
|
||||
if ((behavior.VarArea) && (area > testtri.triangle.area) && (testtri.triangle.area > 0.0))
|
||||
{
|
||||
// Add this triangle to the list of bad triangles.
|
||||
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether the user thinks this triangle is too large.
|
||||
if (behavior.UserTest != null)
|
||||
{
|
||||
if (behavior.UserTest(testtri.triangle, area))
|
||||
{
|
||||
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find the maximum edge and accordingly the pqr orientation
|
||||
if ((apexlen > orglen) && (apexlen > destlen))
|
||||
{
|
||||
// The edge opposite the apex is longest.
|
||||
// maxedge = apexlen;
|
||||
// Find the cosine of the angle at the apex.
|
||||
maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen * destlen));
|
||||
}
|
||||
else if (orglen > destlen)
|
||||
{
|
||||
// The edge opposite the origin is longest.
|
||||
// maxedge = orglen;
|
||||
// Find the cosine of the angle at the origin.
|
||||
maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen * destlen));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The edge opposite the destination is longest.
|
||||
// maxedge = destlen;
|
||||
// Find the cosine of the angle at the destination.
|
||||
maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen * orglen));
|
||||
}
|
||||
|
||||
// Check whether the angle is smaller than permitted.
|
||||
if ((angle > behavior.goodAngle) || (maxangle < behavior.maxGoodAngle && behavior.MaxAngle != 0.0))
|
||||
{
|
||||
// Use the rules of Miller, Pav, and Walkington to decide that certain
|
||||
// triangles should not be split, even if they have bad angles.
|
||||
// A skinny triangle is not split if its shortest edge subtends a
|
||||
// small input angle, and both endpoints of the edge lie on a
|
||||
// concentric circular shell. For convenience, I make a small
|
||||
// adjustment to that rule: I check if the endpoints of the edge
|
||||
// both lie in segment interiors, equidistant from the apex where
|
||||
// the two segments meet.
|
||||
// First, check if both points lie in segment interiors.
|
||||
if ((base1.type == VertexType.SegmentVertex) &&
|
||||
(base2.type == VertexType.SegmentVertex))
|
||||
{
|
||||
// Check if both points lie in a common segment. If they do, the
|
||||
// skinny triangle is enqueued to be split as usual.
|
||||
tri1.SegPivot(ref testsub);
|
||||
if (testsub.seg == Mesh.dummysub)
|
||||
{
|
||||
// No common segment. Find a subsegment that contains 'torg'.
|
||||
tri1.Copy(ref tri2);
|
||||
do
|
||||
{
|
||||
tri1.OprevSelf();
|
||||
tri1.SegPivot(ref testsub);
|
||||
} while (testsub.seg == Mesh.dummysub);
|
||||
// Find the endpoints of the containing segment.
|
||||
org1 = testsub.SegOrg();
|
||||
dest1 = testsub.SegDest();
|
||||
// Find a subsegment that contains 'tdest'.
|
||||
do
|
||||
{
|
||||
tri2.DnextSelf();
|
||||
tri2.SegPivot(ref testsub);
|
||||
} while (testsub.seg == Mesh.dummysub);
|
||||
// Find the endpoints of the containing segment.
|
||||
org2 = testsub.SegOrg();
|
||||
dest2 = testsub.SegDest();
|
||||
// Check if the two containing segments have an endpoint in common.
|
||||
joinvertex = null;
|
||||
if ((dest1.x == org2.x) && (dest1.y == org2.y))
|
||||
{
|
||||
joinvertex = dest1;
|
||||
}
|
||||
else if ((org1.x == dest2.x) && (org1.y == dest2.y))
|
||||
{
|
||||
joinvertex = org1;
|
||||
}
|
||||
if (joinvertex != null)
|
||||
{
|
||||
// Compute the distance from the common endpoint (of the two
|
||||
// segments) to each of the endpoints of the shortest edge.
|
||||
dist1 = ((base1.x - joinvertex.x) * (base1.x - joinvertex.x) +
|
||||
(base1.y - joinvertex.y) * (base1.y - joinvertex.y));
|
||||
dist2 = ((base2.x - joinvertex.x) * (base2.x - joinvertex.x) +
|
||||
(base2.y - joinvertex.y) * (base2.y - joinvertex.y));
|
||||
// If the two distances are equal, don't split the triangle.
|
||||
if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2))
|
||||
{
|
||||
// Return now to avoid enqueueing the bad triangle.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add this triangle to the list of bad triangles.
|
||||
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Maintanance
|
||||
|
||||
/// <summary>
|
||||
/// Traverse the entire list of subsegments, and check each to see if it
|
||||
/// is encroached. If so, add it to the list.
|
||||
/// </summary>
|
||||
private void TallyEncs()
|
||||
{
|
||||
Osub subsegloop = default(Osub);
|
||||
subsegloop.orient = 0;
|
||||
|
||||
foreach (var seg in mesh.subsegs.Values)
|
||||
{
|
||||
subsegloop.seg = seg;
|
||||
// If the segment is encroached, add it to the list.
|
||||
CheckSeg4Encroach(ref subsegloop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Split all the encroached subsegments.
|
||||
/// </summary>
|
||||
/// <param name="triflaws">A flag that specifies whether one should take
|
||||
/// note of new bad triangles that result from inserting vertices to repair
|
||||
/// encroached subsegments.</param>
|
||||
/// <remarks>
|
||||
/// Each encroached subsegment is repaired by splitting it - inserting a
|
||||
/// vertex at or near its midpoint. Newly inserted vertices may encroach
|
||||
/// upon other subsegments; these are also repaired.
|
||||
/// </remarks>
|
||||
private void SplitEncSegs(bool triflaws)
|
||||
{
|
||||
Otri enctri = default(Otri);
|
||||
Otri testtri = default(Otri);
|
||||
Osub testsh = default(Osub);
|
||||
Osub currentenc = default(Osub);
|
||||
BadSubseg seg;
|
||||
Vertex eorg, edest, eapex;
|
||||
Vertex newvertex;
|
||||
InsertVertexResult success;
|
||||
double segmentlength, nearestpoweroftwo;
|
||||
double split;
|
||||
double multiplier, divisor;
|
||||
bool acuteorg, acuteorg2, acutedest, acutedest2;
|
||||
|
||||
// Note that steinerleft == -1 if an unlimited number
|
||||
// of Steiner points is allowed.
|
||||
while (badsubsegs.Count > 0)
|
||||
{
|
||||
if (mesh.steinerleft == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
seg = badsubsegs.Dequeue();
|
||||
|
||||
currentenc = seg.encsubseg;
|
||||
eorg = currentenc.Org();
|
||||
edest = currentenc.Dest();
|
||||
// Make sure that this segment is still the same segment it was
|
||||
// when it was determined to be encroached. If the segment was
|
||||
// enqueued multiple times (because several newly inserted
|
||||
// vertices encroached it), it may have already been split.
|
||||
if (!Osub.IsDead(currentenc.seg) && (eorg == seg.subsegorg) && (edest == seg.subsegdest))
|
||||
{
|
||||
// To decide where to split a segment, we need to know if the
|
||||
// segment shares an endpoint with an adjacent segment.
|
||||
// The concern is that, if we simply split every encroached
|
||||
// segment in its center, two adjacent segments with a small
|
||||
// angle between them might lead to an infinite loop; each
|
||||
// vertex added to split one segment will encroach upon the
|
||||
// other segment, which must then be split with a vertex that
|
||||
// will encroach upon the first segment, and so on forever.
|
||||
// To avoid this, imagine a set of concentric circles, whose
|
||||
// radii are powers of two, about each segment endpoint.
|
||||
// These concentric circles determine where the segment is
|
||||
// split. (If both endpoints are shared with adjacent
|
||||
// segments, split the segment in the middle, and apply the
|
||||
// concentric circles for later splittings.)
|
||||
|
||||
// Is the origin shared with another segment?
|
||||
currentenc.TriPivot(ref enctri);
|
||||
enctri.Lnext(ref testtri);
|
||||
testtri.SegPivot(ref testsh);
|
||||
acuteorg = testsh.seg != Mesh.dummysub;
|
||||
// Is the destination shared with another segment?
|
||||
testtri.LnextSelf();
|
||||
testtri.SegPivot(ref testsh);
|
||||
acutedest = testsh.seg != Mesh.dummysub;
|
||||
|
||||
// If we're using Chew's algorithm (rather than Ruppert's)
|
||||
// to define encroachment, delete free vertices from the
|
||||
// subsegment's diametral circle.
|
||||
if (!behavior.ConformingDelaunay && !acuteorg && !acutedest)
|
||||
{
|
||||
eapex = enctri.Apex();
|
||||
while ((eapex.type == VertexType.FreeVertex) &&
|
||||
((eorg.x - eapex.x) * (edest.x - eapex.x) +
|
||||
(eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0))
|
||||
{
|
||||
mesh.DeleteVertex(ref testtri);
|
||||
currentenc.TriPivot(ref enctri);
|
||||
eapex = enctri.Apex();
|
||||
enctri.Lprev(ref testtri);
|
||||
}
|
||||
}
|
||||
|
||||
// Now, check the other side of the segment, if there's a triangle there.
|
||||
enctri.Sym(ref testtri);
|
||||
if (testtri.triangle != Mesh.dummytri)
|
||||
{
|
||||
// Is the destination shared with another segment?
|
||||
testtri.LnextSelf();
|
||||
testtri.SegPivot(ref testsh);
|
||||
acutedest2 = testsh.seg != Mesh.dummysub;
|
||||
acutedest = acutedest || acutedest2;
|
||||
// Is the origin shared with another segment?
|
||||
testtri.LnextSelf();
|
||||
testtri.SegPivot(ref testsh);
|
||||
acuteorg2 = testsh.seg != Mesh.dummysub;
|
||||
acuteorg = acuteorg || acuteorg2;
|
||||
|
||||
// Delete free vertices from the subsegment's diametral circle.
|
||||
if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2)
|
||||
{
|
||||
eapex = testtri.Org();
|
||||
while ((eapex.type == VertexType.FreeVertex) &&
|
||||
((eorg.x - eapex.x) * (edest.x - eapex.x) +
|
||||
(eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0))
|
||||
{
|
||||
mesh.DeleteVertex(ref testtri);
|
||||
enctri.Sym(ref testtri);
|
||||
eapex = testtri.Apex();
|
||||
testtri.LprevSelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the concentric circles if exactly one endpoint is shared
|
||||
// with another adjacent segment.
|
||||
if (acuteorg || acutedest)
|
||||
{
|
||||
segmentlength = Math.Sqrt((edest.x - eorg.x) * (edest.x - eorg.x) +
|
||||
(edest.y - eorg.y) * (edest.y - eorg.y));
|
||||
// Find the power of two that most evenly splits the segment.
|
||||
// The worst case is a 2:1 ratio between subsegment lengths.
|
||||
nearestpoweroftwo = 1.0;
|
||||
while (segmentlength > 3.0 * nearestpoweroftwo)
|
||||
{
|
||||
nearestpoweroftwo *= 2.0;
|
||||
}
|
||||
while (segmentlength < 1.5 * nearestpoweroftwo)
|
||||
{
|
||||
nearestpoweroftwo *= 0.5;
|
||||
}
|
||||
// Where do we split the segment?
|
||||
split = nearestpoweroftwo / segmentlength;
|
||||
if (acutedest)
|
||||
{
|
||||
split = 1.0 - split;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we're not worried about adjacent segments, split
|
||||
// this segment in the middle.
|
||||
split = 0.5;
|
||||
}
|
||||
|
||||
// Create the new vertex (interpolate coordinates).
|
||||
newvertex = new Vertex(
|
||||
eorg.x + split * (edest.x - eorg.x),
|
||||
eorg.y + split * (edest.y - eorg.y),
|
||||
currentenc.Mark(),
|
||||
mesh.nextras);
|
||||
|
||||
newvertex.type = VertexType.SegmentVertex;
|
||||
|
||||
newvertex.hash = mesh.hash_vtx++;
|
||||
newvertex.id = newvertex.hash;
|
||||
|
||||
mesh.vertices.Add(newvertex.hash, newvertex);
|
||||
|
||||
// Interpolate attributes.
|
||||
for (int i = 0; i < mesh.nextras; i++)
|
||||
{
|
||||
newvertex.attributes[i] = eorg.attributes[i]
|
||||
+ split * (edest.attributes[i] - eorg.attributes[i]);
|
||||
}
|
||||
|
||||
if (!Behavior.NoExact)
|
||||
{
|
||||
// Roundoff in the above calculation may yield a 'newvertex'
|
||||
// that is not precisely collinear with 'eorg' and 'edest'.
|
||||
// Improve collinearity by one step of iterative refinement.
|
||||
multiplier = Primitives.CounterClockwise(eorg, edest, newvertex);
|
||||
divisor = ((eorg.x - edest.x) * (eorg.x - edest.x) +
|
||||
(eorg.y - edest.y) * (eorg.y - edest.y));
|
||||
if ((multiplier != 0.0) && (divisor != 0.0))
|
||||
{
|
||||
multiplier = multiplier / divisor;
|
||||
// Watch out for NANs.
|
||||
if (!double.IsNaN(multiplier))
|
||||
{
|
||||
newvertex.x += multiplier * (edest.y - eorg.y);
|
||||
newvertex.y += multiplier * (eorg.x - edest.x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the new vertex lies on an endpoint.
|
||||
if (((newvertex.x == eorg.x) && (newvertex.y == eorg.y)) ||
|
||||
((newvertex.x == edest.x) && (newvertex.y == edest.y)))
|
||||
{
|
||||
|
||||
logger.Error("Ran out of precision: I attempted to split a"
|
||||
+ " segment to a smaller size than can be accommodated by"
|
||||
+ " the finite precision of floating point arithmetic.",
|
||||
"Quality.SplitEncSegs()");
|
||||
|
||||
throw new Exception("Ran out of precision");
|
||||
}
|
||||
// Insert the splitting vertex. This should always succeed.
|
||||
success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws);
|
||||
if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching))
|
||||
{
|
||||
logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()");
|
||||
throw new Exception("Failure to split a segment.");
|
||||
}
|
||||
if (mesh.steinerleft > 0)
|
||||
{
|
||||
mesh.steinerleft--;
|
||||
}
|
||||
// Check the two new subsegments to see if they're encroached.
|
||||
CheckSeg4Encroach(ref currentenc);
|
||||
currentenc.NextSelf();
|
||||
CheckSeg4Encroach(ref currentenc);
|
||||
}
|
||||
|
||||
// Set subsegment's origin to NULL. This makes it possible to detect dead
|
||||
// badsubsegs when traversing the list of all badsubsegs.
|
||||
seg.subsegorg = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test every triangle in the mesh for quality measures.
|
||||
/// </summary>
|
||||
private void TallyFaces()
|
||||
{
|
||||
Otri triangleloop = default(Otri);
|
||||
triangleloop.orient = 0;
|
||||
|
||||
foreach (var tri in mesh.triangles.Values)
|
||||
{
|
||||
triangleloop.triangle = tri;
|
||||
|
||||
// If the triangle is bad, enqueue it.
|
||||
TestTriangle(ref triangleloop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a vertex at the circumcenter of a triangle. Deletes
|
||||
/// the newly inserted vertex if it encroaches upon a segment.
|
||||
/// </summary>
|
||||
/// <param name="badtri"></param>
|
||||
private void SplitTriangle(BadTriangle badtri)
|
||||
{
|
||||
Otri badotri = default(Otri);
|
||||
Vertex borg, bdest, bapex;
|
||||
Point newloc; // Location of the new vertex
|
||||
double xi = 0, eta = 0;
|
||||
InsertVertexResult success;
|
||||
bool errorflag;
|
||||
|
||||
badotri = badtri.poortri;
|
||||
borg = badotri.Org();
|
||||
bdest = badotri.Dest();
|
||||
bapex = badotri.Apex();
|
||||
|
||||
// Make sure that this triangle is still the same triangle it was
|
||||
// when it was tested and determined to be of bad quality.
|
||||
// Subsequent transformations may have made it a different triangle.
|
||||
if (!Otri.IsDead(badotri.triangle) && (borg == badtri.triangorg) &&
|
||||
(bdest == badtri.triangdest) && (bapex == badtri.triangapex))
|
||||
{
|
||||
errorflag = false;
|
||||
// Create a new vertex at the triangle's circumcenter.
|
||||
|
||||
// Using the original (simpler) Steiner point location method
|
||||
// for mesh refinement.
|
||||
// TODO: NewLocation doesn't work for refinement. Why? Maybe
|
||||
// reset VertexType?
|
||||
if (behavior.fixedArea || behavior.VarArea)
|
||||
{
|
||||
newloc = Primitives.FindCircumcenter(borg, bdest, bapex, ref xi, ref eta, behavior.offconstant);
|
||||
}
|
||||
else
|
||||
{
|
||||
newloc = newLocation.FindLocation(borg, bdest, bapex, ref xi, ref eta, true, badotri);
|
||||
}
|
||||
|
||||
// Check whether the new vertex lies on a triangle vertex.
|
||||
if (((newloc.x == borg.x) && (newloc.y == borg.y)) ||
|
||||
((newloc.x == bdest.x) && (newloc.y == bdest.y)) ||
|
||||
((newloc.x == bapex.x) && (newloc.y == bapex.y)))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
|
||||
errorflag = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The new vertex must be in the interior, and therefore is a
|
||||
// free vertex with a marker of zero.
|
||||
Vertex newvertex = new Vertex(newloc.x, newloc.y, 0, mesh.nextras);
|
||||
newvertex.type = VertexType.FreeVertex;
|
||||
|
||||
for (int i = 0; i < mesh.nextras; i++)
|
||||
{
|
||||
// Interpolate the vertex attributes at the circumcenter.
|
||||
newvertex.attributes[i] = borg.attributes[i]
|
||||
+ xi * (bdest.attributes[i] - borg.attributes[i])
|
||||
+ eta * (bapex.attributes[i] - borg.attributes[i]);
|
||||
}
|
||||
|
||||
// Ensure that the handle 'badotri' does not represent the longest
|
||||
// edge of the triangle. This ensures that the circumcenter must
|
||||
// fall to the left of this edge, so point location will work.
|
||||
// (If the angle org-apex-dest exceeds 90 degrees, then the
|
||||
// circumcenter lies outside the org-dest edge, and eta is
|
||||
// negative. Roundoff error might prevent eta from being
|
||||
// negative when it should be, so I test eta against xi.)
|
||||
if (eta < xi)
|
||||
{
|
||||
badotri.LprevSelf();
|
||||
}
|
||||
|
||||
// Insert the circumcenter, searching from the edge of the triangle,
|
||||
// and maintain the Delaunay property of the triangulation.
|
||||
Osub tmp = default(Osub);
|
||||
success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true);
|
||||
|
||||
if (success == InsertVertexResult.Successful)
|
||||
{
|
||||
newvertex.hash = mesh.hash_vtx++;
|
||||
newvertex.id = newvertex.hash;
|
||||
|
||||
mesh.vertices.Add(newvertex.hash, newvertex);
|
||||
|
||||
if (mesh.steinerleft > 0)
|
||||
{
|
||||
mesh.steinerleft--;
|
||||
}
|
||||
}
|
||||
else if (success == InsertVertexResult.Encroaching)
|
||||
{
|
||||
// If the newly inserted vertex encroaches upon a subsegment,
|
||||
// delete the new vertex.
|
||||
mesh.UndoVertex();
|
||||
}
|
||||
else if (success == InsertVertexResult.Violating)
|
||||
{
|
||||
// Failed to insert the new vertex, but some subsegment was
|
||||
// marked as being encroached.
|
||||
}
|
||||
else
|
||||
{ // success == DUPLICATEVERTEX
|
||||
// Couldn't insert the new vertex because a vertex is already there.
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
|
||||
errorflag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errorflag)
|
||||
{
|
||||
logger.Error("The new vertex is at the circumcenter of triangle: This probably "
|
||||
+ "means that I am trying to refine triangles to a smaller size than can be "
|
||||
+ "accommodated by the finite precision of floating point arithmetic.",
|
||||
"Quality.SplitTriangle()");
|
||||
|
||||
throw new Exception("The new vertex is at the circumcenter of triangle.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all the encroached subsegments and bad triangles from the triangulation.
|
||||
/// </summary>
|
||||
public void EnforceQuality()
|
||||
{
|
||||
BadTriangle badtri;
|
||||
|
||||
// Test all segments to see if they're encroached.
|
||||
TallyEncs();
|
||||
|
||||
// Fix encroached subsegments without noting bad triangles.
|
||||
SplitEncSegs(false);
|
||||
// At this point, if we haven't run out of Steiner points, the
|
||||
// triangulation should be (conforming) Delaunay.
|
||||
|
||||
// Next, we worry about enforcing triangle quality.
|
||||
if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.UserTest != null)
|
||||
{
|
||||
// TODO: Reset queue? (Or is it always empty at this point)
|
||||
|
||||
// Test all triangles to see if they're bad.
|
||||
TallyFaces();
|
||||
|
||||
mesh.checkquality = true;
|
||||
while ((queue.Count > 0) && (mesh.steinerleft != 0))
|
||||
{
|
||||
// Fix one bad triangle by inserting a vertex at its circumcenter.
|
||||
badtri = queue.Dequeue();
|
||||
SplitTriangle(badtri);
|
||||
|
||||
if (badsubsegs.Count > 0)
|
||||
{
|
||||
// Put bad triangle back in queue for another try later.
|
||||
queue.Enqueue(badtri);
|
||||
// Fix any encroached subsegments that resulted.
|
||||
// Record any new bad triangles that result.
|
||||
SplitEncSegs(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, if the "-D" switch was selected and we haven't run out
|
||||
// of Steiner points, the triangulation should be (conforming) Delaunay
|
||||
// and have no low-quality triangles.
|
||||
|
||||
// Might we have run out of Steiner points too soon?
|
||||
if (Behavior.Verbose && behavior.ConformingDelaunay && (badsubsegs.Count > 0) && (mesh.steinerleft == 0))
|
||||
{
|
||||
|
||||
logger.Warning("I ran out of Steiner points, but the mesh has encroached subsegments, "
|
||||
+ "and therefore might not be truly Delaunay. If the Delaunay property is important "
|
||||
+ "to you, try increasing the number of Steiner points.",
|
||||
"Quality.EnforceQuality()");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
namespace TriangleNet.Meshing
|
||||
{
|
||||
using System;
|
||||
using TriangleNet.Geometry;
|
||||
|
||||
public class QualityOptions
|
||||
{
|
||||
public static QualityOptions Empty
|
||||
{
|
||||
get { return new QualityOptions(); }
|
||||
}
|
||||
|
||||
#region Public properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a maximum angle constraint.
|
||||
/// </summary>
|
||||
public double MaximumAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a minimum angle constraint.
|
||||
/// </summary>
|
||||
public double MinimumAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a maximum triangle area constraint.
|
||||
/// </summary>
|
||||
public double MaximumArea { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Apply a user-defined triangle constraint.
|
||||
/// </summary>
|
||||
public Func<ITriangle, double, bool> UserTest { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user