Some code reorganization
git-svn-id: https://triangle.svn.codeplex.com/svn@74818 0e2699bc-83d4-4a8f-98e7-55e24ab8c7a5
This commit is contained in:
@@ -728,11 +728,14 @@ namespace MeshExplorer
|
||||
{
|
||||
if (mesh != null)
|
||||
{
|
||||
bool isConsistent, isDelaunay;
|
||||
bool save = Behavior.Verbose;
|
||||
|
||||
Behavior.Verbose = true;
|
||||
mesh.Check(out isConsistent, out isDelaunay);
|
||||
Behavior.Verbose = false;
|
||||
|
||||
bool isConsistent = MeshValidator.IsConsistent(mesh);
|
||||
bool isDelaunay = MeshValidator.IsDelaunay(mesh);
|
||||
|
||||
Behavior.Verbose = save;
|
||||
|
||||
ShowLog();
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace MeshExplorer.Generators
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
input.AddPoint(r * Math.Cos(i * step), r * Math.Sin(i * step));
|
||||
input.AddSegment(i, (i + 1) % m);
|
||||
input.AddSegment(i, (i + 1) % m, 1);
|
||||
}
|
||||
|
||||
r = 1.5 * r;
|
||||
@@ -81,7 +81,7 @@ namespace MeshExplorer.Generators
|
||||
}
|
||||
|
||||
input.AddPoint(ro * Math.Cos(i * step + offset), ro * Math.Sin(i * step + offset));
|
||||
input.AddSegment(m + i, m + ((i + 1) % n));
|
||||
input.AddSegment(m + i, m + ((i + 1) % n), 2);
|
||||
}
|
||||
|
||||
input.AddHole(0, 0);
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
|
||||
namespace MeshExplorer.IO
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using TriangleNet.Geometry;
|
||||
|
||||
/// <summary>
|
||||
@@ -17,8 +16,18 @@ namespace MeshExplorer.IO
|
||||
/// </summary>
|
||||
public static class GeometryWriter
|
||||
{
|
||||
public static void Write(InputGeometry geometry, string filename)
|
||||
private static int OFFSET = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Writes an InputGeometry to a Triangle format .poly file.
|
||||
/// </summary>
|
||||
/// <param name="geometry">The InputGeometry to write.</param>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="compatibleMode">If true, indices will start at 1 (compatible with original C code).</param>
|
||||
public static void Write(InputGeometry geometry, string filename, bool compatibleMode = false)
|
||||
{
|
||||
OFFSET = compatibleMode ? 1 : 0;
|
||||
|
||||
using (StreamWriter writer = new StreamWriter(filename))
|
||||
{
|
||||
WritePoints(writer, geometry.Points, geometry.Count);
|
||||
@@ -29,7 +38,7 @@ namespace MeshExplorer.IO
|
||||
|
||||
private static void WritePoints(StreamWriter writer, IEnumerable<Point> points, int count)
|
||||
{
|
||||
int attributes = 0, index = 0;
|
||||
int attributes = 0, index = OFFSET;
|
||||
|
||||
var first = points.FirstOrDefault();
|
||||
|
||||
@@ -60,13 +69,13 @@ namespace MeshExplorer.IO
|
||||
|
||||
private static void WriteSegments(StreamWriter writer, IEnumerable<Edge> edges)
|
||||
{
|
||||
int index = 0;
|
||||
int index = OFFSET;
|
||||
|
||||
writer.WriteLine("{0} {1}", edges.Count(), 1);
|
||||
|
||||
foreach (var item in edges)
|
||||
{
|
||||
writer.WriteLine("{0} {1} {2} {3}", index, item.P0, item.P1, item.Boundary);
|
||||
writer.WriteLine("{0} {1} {2} {3}", index, item.P0 + OFFSET, item.P1 + OFFSET, item.Boundary);
|
||||
|
||||
index++;
|
||||
}
|
||||
@@ -74,7 +83,7 @@ namespace MeshExplorer.IO
|
||||
|
||||
private static void WriteHoles(StreamWriter writer, IEnumerable<Point> holes)
|
||||
{
|
||||
int index = 0;
|
||||
int index = OFFSET;
|
||||
|
||||
writer.WriteLine("{0}", holes.Count());
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
namespace TriangleNet
|
||||
{
|
||||
using System;
|
||||
using TriangleNet.Geometry;
|
||||
using TriangleNet.Log;
|
||||
|
||||
/// <summary>
|
||||
@@ -20,7 +21,6 @@ namespace TriangleNet
|
||||
bool poly = false;
|
||||
bool quality = false;
|
||||
bool varArea = false;
|
||||
bool usertest = false;
|
||||
bool convex = false;
|
||||
bool jettison = false;
|
||||
bool boundaryMarkers = true;
|
||||
@@ -28,6 +28,8 @@ namespace TriangleNet
|
||||
bool conformDel = false;
|
||||
TriangulationAlgorithm algorithm = TriangulationAlgorithm.Dwyer;
|
||||
|
||||
Func<ITriangle, double, bool> usertest;
|
||||
|
||||
int noBisect = 0;
|
||||
int steiner = -1;
|
||||
|
||||
@@ -181,7 +183,7 @@ namespace TriangleNet
|
||||
/// <summary>
|
||||
/// Apply a user-defined triangle constraint.
|
||||
/// </summary>
|
||||
public bool Usertest
|
||||
public Func<ITriangle, double, bool> UserTest
|
||||
{
|
||||
get { return usertest; }
|
||||
set { usertest = value; }
|
||||
|
||||
@@ -0,0 +1,776 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="ConstraintMesher.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
|
||||
{
|
||||
using System;
|
||||
using TriangleNet.Data;
|
||||
using TriangleNet.Geometry;
|
||||
using TriangleNet.Log;
|
||||
|
||||
internal class ConstraintMesher
|
||||
{
|
||||
Mesh mesh;
|
||||
Behavior behavior;
|
||||
TriangleLocator locator;
|
||||
|
||||
ILog<SimpleLogItem> logger;
|
||||
|
||||
public ConstraintMesher(Mesh mesh)
|
||||
{
|
||||
this.mesh = mesh;
|
||||
this.behavior = mesh.behavior;
|
||||
this.locator = mesh.locator;
|
||||
|
||||
logger = SimpleLog.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the segments of a triangulation, including PSLG segments and edges
|
||||
/// on the convex hull.
|
||||
/// </summary>
|
||||
/// <param name="segmentlist"></param>
|
||||
/// <param name="segmentmarkerlist"></param>
|
||||
/// <param name="numberofsegments"></param>
|
||||
public void FormSkeleton(InputGeometry input)
|
||||
{
|
||||
Vertex endpoint1, endpoint2;
|
||||
int end1, end2;
|
||||
int boundmarker;
|
||||
|
||||
mesh.insegments = 0;
|
||||
|
||||
if (behavior.Poly)
|
||||
{
|
||||
// If the input vertices are collinear, there is no triangulation,
|
||||
// so don't try to insert segments.
|
||||
if (mesh.triangles.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If segments are to be inserted, compute a mapping
|
||||
// from vertices to triangles.
|
||||
if (input.HasSegments)
|
||||
{
|
||||
mesh.MakeVertexMap();
|
||||
}
|
||||
|
||||
boundmarker = 0;
|
||||
|
||||
// Read and insert the segments.
|
||||
foreach (var seg in input.segments)
|
||||
{
|
||||
mesh.insegments++;
|
||||
|
||||
end1 = seg.P0;
|
||||
end2 = seg.P1;
|
||||
boundmarker = seg.Boundary;
|
||||
|
||||
if ((end1 < 0) || (end1 >= mesh.invertices))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
logger.Warning("Invalid first endpoint of segment.", "Mesh.FormSkeleton().1");
|
||||
}
|
||||
}
|
||||
else if ((end2 < 0) || (end2 >= mesh.invertices))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
logger.Warning("Invalid second endpoint of segment.", "Mesh.FormSkeleton().2");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Is using the vertex ID reliable???
|
||||
// It should be. The ID gets appropriately set in TransferNodes().
|
||||
|
||||
// Find the vertices numbered 'end1' and 'end2'.
|
||||
endpoint1 = mesh.vertices[end1];
|
||||
endpoint2 = mesh.vertices[end2];
|
||||
if ((endpoint1.x == endpoint2.x) && (endpoint1.y == endpoint2.y))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
logger.Warning("Endpoints of segment (IDs " + end1 + "/" + end2 + ") are coincident.",
|
||||
"Mesh.FormSkeleton()");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InsertSegment(endpoint1, endpoint2, boundmarker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (behavior.Convex || !behavior.Poly)
|
||||
{
|
||||
// Enclose the convex hull with subsegments.
|
||||
MarkHull();
|
||||
}
|
||||
}
|
||||
|
||||
#region Segment insertion
|
||||
|
||||
/// <summary>
|
||||
/// Find the first triangle on the path from one point to another.
|
||||
/// </summary>
|
||||
/// <param name="searchtri"></param>
|
||||
/// <param name="searchpoint"></param>
|
||||
/// <returns>
|
||||
/// The return value notes whether the destination or apex of the found
|
||||
/// triangle is collinear with the two points in question.</returns>
|
||||
/// <remarks>
|
||||
/// Finds the triangle that intersects a line segment drawn from the
|
||||
/// origin of 'searchtri' to the point 'searchpoint', and returns the result
|
||||
/// in 'searchtri'. The origin of 'searchtri' does not change, even though
|
||||
/// the triangle returned may differ from the one passed in. This routine
|
||||
/// is used to find the direction to move in to get from one point to
|
||||
/// another.
|
||||
/// </remarks>
|
||||
private FindDirectionResult FindDirection(ref Otri searchtri, Vertex searchpoint)
|
||||
{
|
||||
Otri checktri = default(Otri);
|
||||
Vertex startvertex;
|
||||
Vertex leftvertex, rightvertex;
|
||||
double leftccw, rightccw;
|
||||
bool leftflag, rightflag;
|
||||
|
||||
startvertex = searchtri.Org();
|
||||
rightvertex = searchtri.Dest();
|
||||
leftvertex = searchtri.Apex();
|
||||
// Is 'searchpoint' to the left?
|
||||
leftccw = Primitives.CounterClockwise(searchpoint, startvertex, leftvertex);
|
||||
leftflag = leftccw > 0.0;
|
||||
// Is 'searchpoint' to the right?
|
||||
rightccw = Primitives.CounterClockwise(startvertex, searchpoint, rightvertex);
|
||||
rightflag = rightccw > 0.0;
|
||||
if (leftflag && rightflag)
|
||||
{
|
||||
// 'searchtri' faces directly away from 'searchpoint'. We could go left
|
||||
// or right. Ask whether it's a triangle or a boundary on the left.
|
||||
searchtri.Onext(ref checktri);
|
||||
if (checktri.triangle == Mesh.dummytri)
|
||||
{
|
||||
leftflag = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
rightflag = false;
|
||||
}
|
||||
}
|
||||
while (leftflag)
|
||||
{
|
||||
// Turn left until satisfied.
|
||||
searchtri.OnextSelf();
|
||||
if (searchtri.triangle == Mesh.dummytri)
|
||||
{
|
||||
logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().1");
|
||||
throw new Exception("Unable to find a triangle on path.");
|
||||
}
|
||||
leftvertex = searchtri.Apex();
|
||||
rightccw = leftccw;
|
||||
leftccw = Primitives.CounterClockwise(searchpoint, startvertex, leftvertex);
|
||||
leftflag = leftccw > 0.0;
|
||||
}
|
||||
while (rightflag)
|
||||
{
|
||||
// Turn right until satisfied.
|
||||
searchtri.OprevSelf();
|
||||
if (searchtri.triangle == Mesh.dummytri)
|
||||
{
|
||||
logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().2");
|
||||
throw new Exception("Unable to find a triangle on path.");
|
||||
}
|
||||
rightvertex = searchtri.Dest();
|
||||
leftccw = rightccw;
|
||||
rightccw = Primitives.CounterClockwise(startvertex, searchpoint, rightvertex);
|
||||
rightflag = rightccw > 0.0;
|
||||
}
|
||||
if (leftccw == 0.0)
|
||||
{
|
||||
return FindDirectionResult.Leftcollinear;
|
||||
}
|
||||
else if (rightccw == 0.0)
|
||||
{
|
||||
return FindDirectionResult.Rightcollinear;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FindDirectionResult.Within;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the intersection of an existing segment and a segment that is being
|
||||
/// inserted. Insert a vertex at the intersection, splitting an existing subsegment.
|
||||
/// </summary>
|
||||
/// <param name="splittri"></param>
|
||||
/// <param name="splitsubseg"></param>
|
||||
/// <param name="endpoint2"></param>
|
||||
/// <remarks>
|
||||
/// The segment being inserted connects the apex of splittri to endpoint2.
|
||||
/// splitsubseg is the subsegment being split, and MUST adjoin splittri.
|
||||
/// Hence, endpoints of the subsegment being split are the origin and
|
||||
/// destination of splittri.
|
||||
///
|
||||
/// On completion, splittri is a handle having the newly inserted
|
||||
/// intersection point as its origin, and endpoint1 as its destination.
|
||||
/// </remarks>
|
||||
private void SegmentIntersection(ref Otri splittri, ref Osub splitsubseg, Vertex endpoint2)
|
||||
{
|
||||
Osub opposubseg = default(Osub);
|
||||
Vertex endpoint1;
|
||||
Vertex torg, tdest;
|
||||
Vertex leftvertex, rightvertex;
|
||||
Vertex newvertex;
|
||||
InsertVertexResult success;
|
||||
|
||||
double ex, ey;
|
||||
double tx, ty;
|
||||
double etx, ety;
|
||||
double split, denom;
|
||||
|
||||
// Find the other three segment endpoints.
|
||||
endpoint1 = splittri.Apex();
|
||||
torg = splittri.Org();
|
||||
tdest = splittri.Dest();
|
||||
// Segment intersection formulae; see the Antonio reference.
|
||||
tx = tdest.x - torg.x;
|
||||
ty = tdest.y - torg.y;
|
||||
ex = endpoint2.x - endpoint1.x;
|
||||
ey = endpoint2.y - endpoint1.y;
|
||||
etx = torg.x - endpoint2.x;
|
||||
ety = torg.y - endpoint2.y;
|
||||
denom = ty * ex - tx * ey;
|
||||
if (denom == 0.0)
|
||||
{
|
||||
logger.Error("Attempt to find intersection of parallel segments.",
|
||||
"Mesh.SegmentIntersection()");
|
||||
throw new Exception("Attempt to find intersection of parallel segments.");
|
||||
}
|
||||
split = (ey * etx - ex * ety) / denom;
|
||||
|
||||
// Create the new vertex.
|
||||
newvertex = new Vertex(
|
||||
torg.x + split * (tdest.x - torg.x),
|
||||
torg.y + split * (tdest.y - torg.y),
|
||||
splitsubseg.seg.boundary,
|
||||
mesh.nextras);
|
||||
|
||||
newvertex.hash = mesh.hash_vtx++;
|
||||
newvertex.id = newvertex.hash;
|
||||
|
||||
// Interpolate its attributes.
|
||||
for (int i = 0; i < mesh.nextras; i++)
|
||||
{
|
||||
newvertex.attributes[i] = torg.attributes[i] + split * (tdest.attributes[i] - torg.attributes[i]);
|
||||
}
|
||||
|
||||
mesh.vertices.Add(newvertex.hash, newvertex);
|
||||
|
||||
// Insert the intersection vertex. This should always succeed.
|
||||
success = mesh.InsertVertex(newvertex, ref splittri, ref splitsubseg, false, false);
|
||||
if (success != InsertVertexResult.Successful)
|
||||
{
|
||||
logger.Error("Failure to split a segment.", "Mesh.SegmentIntersection()");
|
||||
throw new Exception("Failure to split a segment.");
|
||||
}
|
||||
// Record a triangle whose origin is the new vertex.
|
||||
newvertex.tri = splittri;
|
||||
if (mesh.steinerleft > 0)
|
||||
{
|
||||
mesh.steinerleft--;
|
||||
}
|
||||
|
||||
// Divide the segment into two, and correct the segment endpoints.
|
||||
splitsubseg.SymSelf();
|
||||
splitsubseg.Pivot(ref opposubseg);
|
||||
splitsubseg.Dissolve();
|
||||
opposubseg.Dissolve();
|
||||
do
|
||||
{
|
||||
splitsubseg.SetSegOrg(newvertex);
|
||||
splitsubseg.NextSelf();
|
||||
} while (splitsubseg.seg != Mesh.dummysub);
|
||||
do
|
||||
{
|
||||
opposubseg.SetSegOrg(newvertex);
|
||||
opposubseg.NextSelf();
|
||||
} while (opposubseg.seg != Mesh.dummysub);
|
||||
|
||||
// Inserting the vertex may have caused edge flips. We wish to rediscover
|
||||
// the edge connecting endpoint1 to the new intersection vertex.
|
||||
FindDirection(ref splittri, endpoint1);
|
||||
|
||||
rightvertex = splittri.Dest();
|
||||
leftvertex = splittri.Apex();
|
||||
if ((leftvertex.x == endpoint1.x) && (leftvertex.y == endpoint1.y))
|
||||
{
|
||||
splittri.OnextSelf();
|
||||
}
|
||||
else if ((rightvertex.x != endpoint1.x) || (rightvertex.y != endpoint1.y))
|
||||
{
|
||||
logger.Error("Topological inconsistency after splitting a segment.", "Mesh.SegmentIntersection()");
|
||||
throw new Exception("Topological inconsistency after splitting a segment.");
|
||||
}
|
||||
// 'splittri' should have destination endpoint1.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scout the first triangle on the path from one endpoint to another, and check
|
||||
/// for completion (reaching the second endpoint), a collinear vertex, or the
|
||||
/// intersection of two segments.
|
||||
/// </summary>
|
||||
/// <param name="searchtri"></param>
|
||||
/// <param name="endpoint2"></param>
|
||||
/// <param name="newmark"></param>
|
||||
/// <returns>Returns true if the entire segment is successfully inserted, and false
|
||||
/// if the job must be finished by ConstrainedEdge().</returns>
|
||||
/// <remarks>
|
||||
/// If the first triangle on the path has the second endpoint as its
|
||||
/// destination or apex, a subsegment is inserted and the job is done.
|
||||
///
|
||||
/// If the first triangle on the path has a destination or apex that lies on
|
||||
/// the segment, a subsegment is inserted connecting the first endpoint to
|
||||
/// the collinear vertex, and the search is continued from the collinear
|
||||
/// vertex.
|
||||
///
|
||||
/// If the first triangle on the path has a subsegment opposite its origin,
|
||||
/// then there is a segment that intersects the segment being inserted.
|
||||
/// Their intersection vertex is inserted, splitting the subsegment.
|
||||
/// </remarks>
|
||||
private bool ScoutSegment(ref Otri searchtri, Vertex endpoint2, int newmark)
|
||||
{
|
||||
Otri crosstri = default(Otri);
|
||||
Osub crosssubseg = default(Osub);
|
||||
Vertex leftvertex, rightvertex;
|
||||
FindDirectionResult collinear;
|
||||
|
||||
collinear = FindDirection(ref searchtri, endpoint2);
|
||||
rightvertex = searchtri.Dest();
|
||||
leftvertex = searchtri.Apex();
|
||||
if (((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) ||
|
||||
((rightvertex.x == endpoint2.x) && (rightvertex.y == endpoint2.y)))
|
||||
{
|
||||
// The segment is already an edge in the mesh.
|
||||
if ((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y))
|
||||
{
|
||||
searchtri.LprevSelf();
|
||||
}
|
||||
// Insert a subsegment, if there isn't already one there.
|
||||
mesh.InsertSubseg(ref searchtri, newmark);
|
||||
return true;
|
||||
}
|
||||
else if (collinear == FindDirectionResult.Leftcollinear)
|
||||
{
|
||||
// We've collided with a vertex between the segment's endpoints.
|
||||
// Make the collinear vertex be the triangle's origin.
|
||||
searchtri.LprevSelf();
|
||||
mesh.InsertSubseg(ref searchtri, newmark);
|
||||
// Insert the remainder of the segment.
|
||||
return ScoutSegment(ref searchtri, endpoint2, newmark);
|
||||
}
|
||||
else if (collinear == FindDirectionResult.Rightcollinear)
|
||||
{
|
||||
// We've collided with a vertex between the segment's endpoints.
|
||||
mesh.InsertSubseg(ref searchtri, newmark);
|
||||
// Make the collinear vertex be the triangle's origin.
|
||||
searchtri.LnextSelf();
|
||||
// Insert the remainder of the segment.
|
||||
return ScoutSegment(ref searchtri, endpoint2, newmark);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchtri.Lnext(ref crosstri);
|
||||
crosstri.SegPivot(ref crosssubseg);
|
||||
// Check for a crossing segment.
|
||||
if (crosssubseg.seg == Mesh.dummysub)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert a vertex at the intersection.
|
||||
SegmentIntersection(ref crosstri, ref crosssubseg, endpoint2);
|
||||
crosstri.Copy(ref searchtri);
|
||||
mesh.InsertSubseg(ref searchtri, newmark);
|
||||
// Insert the remainder of the segment.
|
||||
return ScoutSegment(ref searchtri, endpoint2, newmark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enforce the Delaunay condition at an edge, fanning out recursively from
|
||||
/// an existing vertex. Pay special attention to stacking inverted triangles.
|
||||
/// </summary>
|
||||
/// <param name="fixuptri"></param>
|
||||
/// <param name="leftside">Indicates whether or not fixuptri is to the left of
|
||||
/// the segment being inserted. (Imagine that the segment is pointing up from
|
||||
/// endpoint1 to endpoint2.)</param>
|
||||
/// <remarks>
|
||||
/// This is a support routine for inserting segments into a constrained
|
||||
/// Delaunay triangulation.
|
||||
///
|
||||
/// The origin of fixuptri is treated as if it has just been inserted, and
|
||||
/// the local Delaunay condition needs to be enforced. It is only enforced
|
||||
/// in one sector, however, that being the angular range defined by
|
||||
/// fixuptri.
|
||||
///
|
||||
/// This routine also needs to make decisions regarding the "stacking" of
|
||||
/// triangles. (Read the description of ConstrainedEdge() below before
|
||||
/// reading on here, so you understand the algorithm.) If the position of
|
||||
/// the new vertex (the origin of fixuptri) indicates that the vertex before
|
||||
/// it on the polygon is a reflex vertex, then "stack" the triangle by
|
||||
/// doing nothing. (fixuptri is an inverted triangle, which is how stacked
|
||||
/// triangles are identified.)
|
||||
///
|
||||
/// Otherwise, check whether the vertex before that was a reflex vertex.
|
||||
/// If so, perform an edge flip, thereby eliminating an inverted triangle
|
||||
/// (popping it off the stack). The edge flip may result in the creation
|
||||
/// of a new inverted triangle, depending on whether or not the new vertex
|
||||
/// is visible to the vertex three edges behind on the polygon.
|
||||
///
|
||||
/// If neither of the two vertices behind the new vertex are reflex
|
||||
/// vertices, fixuptri and fartri, the triangle opposite it, are not
|
||||
/// inverted; hence, ensure that the edge between them is locally Delaunay.
|
||||
/// </remarks>
|
||||
private void DelaunayFixup(ref Otri fixuptri, bool leftside)
|
||||
{
|
||||
Otri neartri = default(Otri);
|
||||
Otri fartri = default(Otri);
|
||||
Osub faredge = default(Osub);
|
||||
Vertex nearvertex, leftvertex, rightvertex, farvertex;
|
||||
|
||||
fixuptri.Lnext(ref neartri);
|
||||
neartri.Sym(ref fartri);
|
||||
// Check if the edge opposite the origin of fixuptri can be flipped.
|
||||
if (fartri.triangle == Mesh.dummytri)
|
||||
{
|
||||
return;
|
||||
}
|
||||
neartri.SegPivot(ref faredge);
|
||||
if (faredge.seg != Mesh.dummysub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Find all the relevant vertices.
|
||||
nearvertex = neartri.Apex();
|
||||
leftvertex = neartri.Org();
|
||||
rightvertex = neartri.Dest();
|
||||
farvertex = fartri.Apex();
|
||||
// Check whether the previous polygon vertex is a reflex vertex.
|
||||
if (leftside)
|
||||
{
|
||||
if (Primitives.CounterClockwise(nearvertex, leftvertex, farvertex) <= 0.0)
|
||||
{
|
||||
// leftvertex is a reflex vertex too. Nothing can
|
||||
// be done until a convex section is found.
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Primitives.CounterClockwise(farvertex, rightvertex, nearvertex) <= 0.0)
|
||||
{
|
||||
// rightvertex is a reflex vertex too. Nothing can
|
||||
// be done until a convex section is found.
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Primitives.CounterClockwise(rightvertex, leftvertex, farvertex) > 0.0)
|
||||
{
|
||||
// fartri is not an inverted triangle, and farvertex is not a reflex
|
||||
// vertex. As there are no reflex vertices, fixuptri isn't an
|
||||
// inverted triangle, either. Hence, test the edge between the
|
||||
// triangles to ensure it is locally Delaunay.
|
||||
if (Primitives.InCircle(leftvertex, farvertex, rightvertex, nearvertex) <= 0.0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Not locally Delaunay; go on to an edge flip.
|
||||
}
|
||||
// else fartri is inverted; remove it from the stack by flipping.
|
||||
mesh.Flip(ref neartri);
|
||||
fixuptri.LprevSelf(); // Restore the origin of fixuptri after the flip.
|
||||
// Recursively process the two triangles that result from the flip.
|
||||
DelaunayFixup(ref fixuptri, leftside);
|
||||
DelaunayFixup(ref fartri, leftside);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force a segment into a constrained Delaunay triangulation by deleting the
|
||||
/// triangles it intersects, and triangulating the polygons that form on each
|
||||
/// side of it.
|
||||
/// </summary>
|
||||
/// <param name="starttri"></param>
|
||||
/// <param name="endpoint2"></param>
|
||||
/// <param name="newmark"></param>
|
||||
/// <remarks>
|
||||
/// Generates a single subsegment connecting 'endpoint1' to 'endpoint2'.
|
||||
/// The triangle 'starttri' has 'endpoint1' as its origin. 'newmark' is the
|
||||
/// boundary marker of the segment.
|
||||
///
|
||||
/// To insert a segment, every triangle whose interior intersects the
|
||||
/// segment is deleted. The union of these deleted triangles is a polygon
|
||||
/// (which is not necessarily monotone, but is close enough), which is
|
||||
/// divided into two polygons by the new segment. This routine's task is
|
||||
/// to generate the Delaunay triangulation of these two polygons.
|
||||
///
|
||||
/// You might think of this routine's behavior as a two-step process. The
|
||||
/// first step is to walk from endpoint1 to endpoint2, flipping each edge
|
||||
/// encountered. This step creates a fan of edges connected to endpoint1,
|
||||
/// including the desired edge to endpoint2. The second step enforces the
|
||||
/// Delaunay condition on each side of the segment in an incremental manner:
|
||||
/// proceeding along the polygon from endpoint1 to endpoint2 (this is done
|
||||
/// independently on each side of the segment), each vertex is "enforced"
|
||||
/// as if it had just been inserted, but affecting only the previous
|
||||
/// vertices. The result is the same as if the vertices had been inserted
|
||||
/// in the order they appear on the polygon, so the result is Delaunay.
|
||||
///
|
||||
/// In truth, ConstrainedEdge() interleaves these two steps. The procedure
|
||||
/// walks from endpoint1 to endpoint2, and each time an edge is encountered
|
||||
/// and flipped, the newly exposed vertex (at the far end of the flipped
|
||||
/// edge) is "enforced" upon the previously flipped edges, usually affecting
|
||||
/// only one side of the polygon (depending upon which side of the segment
|
||||
/// the vertex falls on).
|
||||
///
|
||||
/// The algorithm is complicated by the need to handle polygons that are not
|
||||
/// convex. Although the polygon is not necessarily monotone, it can be
|
||||
/// triangulated in a manner similar to the stack-based algorithms for
|
||||
/// monotone polygons. For each reflex vertex (local concavity) of the
|
||||
/// polygon, there will be an inverted triangle formed by one of the edge
|
||||
/// flips. (An inverted triangle is one with negative area - that is, its
|
||||
/// vertices are arranged in clockwise order - and is best thought of as a
|
||||
/// wrinkle in the fabric of the mesh.) Each inverted triangle can be
|
||||
/// thought of as a reflex vertex pushed on the stack, waiting to be fixed
|
||||
/// later.
|
||||
///
|
||||
/// A reflex vertex is popped from the stack when a vertex is inserted that
|
||||
/// is visible to the reflex vertex. (However, if the vertex behind the
|
||||
/// reflex vertex is not visible to the reflex vertex, a new inverted
|
||||
/// triangle will take its place on the stack.) These details are handled
|
||||
/// by the DelaunayFixup() routine above.
|
||||
/// </remarks>
|
||||
private void ConstrainedEdge(ref Otri starttri, Vertex endpoint2, int newmark)
|
||||
{
|
||||
Otri fixuptri = default(Otri), fixuptri2 = default(Otri);
|
||||
Osub crosssubseg = default(Osub);
|
||||
Vertex endpoint1;
|
||||
Vertex farvertex;
|
||||
double area;
|
||||
bool collision;
|
||||
bool done;
|
||||
|
||||
endpoint1 = starttri.Org();
|
||||
starttri.Lnext(ref fixuptri);
|
||||
mesh.Flip(ref fixuptri);
|
||||
// 'collision' indicates whether we have found a vertex directly
|
||||
// between endpoint1 and endpoint2.
|
||||
collision = false;
|
||||
done = false;
|
||||
do
|
||||
{
|
||||
farvertex = fixuptri.Org();
|
||||
// 'farvertex' is the extreme point of the polygon we are "digging"
|
||||
// to get from endpoint1 to endpoint2.
|
||||
if ((farvertex.x == endpoint2.x) && (farvertex.y == endpoint2.y))
|
||||
{
|
||||
fixuptri.Oprev(ref fixuptri2);
|
||||
// Enforce the Delaunay condition around endpoint2.
|
||||
DelaunayFixup(ref fixuptri, false);
|
||||
DelaunayFixup(ref fixuptri2, true);
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check whether farvertex is to the left or right of the segment being
|
||||
// inserted, to decide which edge of fixuptri to dig through next.
|
||||
area = Primitives.CounterClockwise(endpoint1, endpoint2, farvertex);
|
||||
if (area == 0.0)
|
||||
{
|
||||
// We've collided with a vertex between endpoint1 and endpoint2.
|
||||
collision = true;
|
||||
fixuptri.Oprev(ref fixuptri2);
|
||||
// Enforce the Delaunay condition around farvertex.
|
||||
DelaunayFixup(ref fixuptri, false);
|
||||
DelaunayFixup(ref fixuptri2, true);
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (area > 0.0)
|
||||
{
|
||||
// farvertex is to the left of the segment.
|
||||
fixuptri.Oprev(ref fixuptri2);
|
||||
// Enforce the Delaunay condition around farvertex, on the
|
||||
// left side of the segment only.
|
||||
DelaunayFixup(ref fixuptri2, true);
|
||||
// Flip the edge that crosses the segment. After the edge is
|
||||
// flipped, one of its endpoints is the fan vertex, and the
|
||||
// destination of fixuptri is the fan vertex.
|
||||
fixuptri.LprevSelf();
|
||||
}
|
||||
else
|
||||
{
|
||||
// farvertex is to the right of the segment.
|
||||
DelaunayFixup(ref fixuptri, false);
|
||||
// Flip the edge that crosses the segment. After the edge is
|
||||
// flipped, one of its endpoints is the fan vertex, and the
|
||||
// destination of fixuptri is the fan vertex.
|
||||
fixuptri.OprevSelf();
|
||||
}
|
||||
// Check for two intersecting segments.
|
||||
fixuptri.SegPivot(ref crosssubseg);
|
||||
if (crosssubseg.seg == Mesh.dummysub)
|
||||
{
|
||||
mesh.Flip(ref fixuptri); // May create inverted triangle at left.
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've collided with a segment between endpoint1 and endpoint2.
|
||||
collision = true;
|
||||
// Insert a vertex at the intersection.
|
||||
SegmentIntersection(ref fixuptri, ref crosssubseg, endpoint2);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!done);
|
||||
// Insert a subsegment to make the segment permanent.
|
||||
mesh.InsertSubseg(ref fixuptri, newmark);
|
||||
// If there was a collision with an interceding vertex, install another
|
||||
// segment connecting that vertex with endpoint2.
|
||||
if (collision)
|
||||
{
|
||||
// Insert the remainder of the segment.
|
||||
if (!ScoutSegment(ref fixuptri, endpoint2, newmark))
|
||||
{
|
||||
ConstrainedEdge(ref fixuptri, endpoint2, newmark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a PSLG segment into a triangulation.
|
||||
/// </summary>
|
||||
/// <param name="endpoint1"></param>
|
||||
/// <param name="endpoint2"></param>
|
||||
/// <param name="newmark"></param>
|
||||
private void InsertSegment(Vertex endpoint1, Vertex endpoint2, int newmark)
|
||||
{
|
||||
Otri searchtri1 = default(Otri), searchtri2 = default(Otri);
|
||||
Vertex checkvertex = null;
|
||||
|
||||
// Find a triangle whose origin is the segment's first endpoint.
|
||||
searchtri1 = endpoint1.tri;
|
||||
if (searchtri1.triangle != null)
|
||||
{
|
||||
checkvertex = searchtri1.Org();
|
||||
}
|
||||
|
||||
if (checkvertex != endpoint1)
|
||||
{
|
||||
// Find a boundary triangle to search from.
|
||||
searchtri1.triangle = Mesh.dummytri;
|
||||
searchtri1.orient = 0;
|
||||
searchtri1.SymSelf();
|
||||
// Search for the segment's first endpoint by point location.
|
||||
if (locator.Locate(endpoint1, ref searchtri1) != LocateResult.OnVertex)
|
||||
{
|
||||
logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().1");
|
||||
throw new Exception("Unable to locate PSLG vertex in triangulation.");
|
||||
}
|
||||
}
|
||||
// Remember this triangle to improve subsequent point location.
|
||||
locator.Update(ref searchtri1);
|
||||
|
||||
// Scout the beginnings of a path from the first endpoint
|
||||
// toward the second.
|
||||
if (ScoutSegment(ref searchtri1, endpoint2, newmark))
|
||||
{
|
||||
// The segment was easily inserted.
|
||||
return;
|
||||
}
|
||||
// The first endpoint may have changed if a collision with an intervening
|
||||
// vertex on the segment occurred.
|
||||
endpoint1 = searchtri1.Org();
|
||||
|
||||
// Find a triangle whose origin is the segment's second endpoint.
|
||||
checkvertex = null;
|
||||
searchtri2 = endpoint2.tri;
|
||||
if (searchtri2.triangle != null)
|
||||
{
|
||||
checkvertex = searchtri2.Org();
|
||||
}
|
||||
if (checkvertex != endpoint2)
|
||||
{
|
||||
// Find a boundary triangle to search from.
|
||||
searchtri2.triangle = Mesh.dummytri;
|
||||
searchtri2.orient = 0;
|
||||
searchtri2.SymSelf();
|
||||
// Search for the segment's second endpoint by point location.
|
||||
if (locator.Locate(endpoint2, ref searchtri2) != LocateResult.OnVertex)
|
||||
{
|
||||
logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().2");
|
||||
throw new Exception("Unable to locate PSLG vertex in triangulation.");
|
||||
}
|
||||
}
|
||||
// Remember this triangle to improve subsequent point location.
|
||||
locator.Update(ref searchtri2);
|
||||
// Scout the beginnings of a path from the second endpoint
|
||||
// toward the first.
|
||||
if (ScoutSegment(ref searchtri2, endpoint1, newmark))
|
||||
{
|
||||
// The segment was easily inserted.
|
||||
return;
|
||||
}
|
||||
// The second endpoint may have changed if a collision with an intervening
|
||||
// vertex on the segment occurred.
|
||||
endpoint2 = searchtri2.Org();
|
||||
|
||||
// Insert the segment directly into the triangulation.
|
||||
ConstrainedEdge(ref searchtri1, endpoint2, newmark);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cover the convex hull of a triangulation with subsegments.
|
||||
/// </summary>
|
||||
private void MarkHull()
|
||||
{
|
||||
Otri hulltri = default(Otri);
|
||||
Otri nexttri = default(Otri);
|
||||
Otri starttri = default(Otri);
|
||||
|
||||
// Find a triangle handle on the hull.
|
||||
hulltri.triangle = Mesh.dummytri;
|
||||
hulltri.orient = 0;
|
||||
hulltri.SymSelf();
|
||||
// Remember where we started so we know when to stop.
|
||||
hulltri.Copy(ref starttri);
|
||||
// Go once counterclockwise around the convex hull.
|
||||
do
|
||||
{
|
||||
// Create a subsegment if there isn't already one here.
|
||||
mesh.InsertSubseg(ref hulltri, 1);
|
||||
// To find the next hull edge, go clockwise around the next vertex.
|
||||
hulltri.LnextSelf();
|
||||
hulltri.Oprev(ref nexttri);
|
||||
while (nexttri.triangle != Mesh.dummytri)
|
||||
{
|
||||
nexttri.Copy(ref hulltri);
|
||||
hulltri.Oprev(ref nexttri);
|
||||
}
|
||||
} while (!hulltri.Equal(starttri));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -340,8 +340,10 @@ namespace TriangleNet
|
||||
// Segments will be introduced next.
|
||||
checksegments = true;
|
||||
|
||||
var mesher = new ConstraintMesher(this);
|
||||
|
||||
// Insert PSLG segments and/or convex hull segments.
|
||||
FormSkeleton(input);
|
||||
mesher.FormSkeleton(input);
|
||||
}
|
||||
|
||||
if (behavior.Poly && (triangles.Count > 0))
|
||||
@@ -545,17 +547,6 @@ namespace TriangleNet
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check mesh consistency and (constrained) Delaunay property.
|
||||
/// </summary>
|
||||
/// <param name="isConsistent">Value indicating if mesh topology is consistent.</param>
|
||||
/// <param name="isDelaunay">Value indicating if mesh is Delaunay.</param>
|
||||
public void Check(out bool isConsistent, out bool isDelaunay)
|
||||
{
|
||||
isConsistent = quality.CheckMesh();
|
||||
isDelaunay = quality.CheckDelaunay();
|
||||
}
|
||||
|
||||
#region Misc
|
||||
|
||||
/// <summary>
|
||||
@@ -1982,750 +1973,6 @@ namespace TriangleNet
|
||||
|
||||
#endregion
|
||||
|
||||
#region Segment insertion
|
||||
|
||||
/// <summary>
|
||||
/// Find the first triangle on the path from one point to another.
|
||||
/// </summary>
|
||||
/// <param name="searchtri"></param>
|
||||
/// <param name="searchpoint"></param>
|
||||
/// <returns>
|
||||
/// The return value notes whether the destination or apex of the found
|
||||
/// triangle is collinear with the two points in question.</returns>
|
||||
/// <remarks>
|
||||
/// Finds the triangle that intersects a line segment drawn from the
|
||||
/// origin of 'searchtri' to the point 'searchpoint', and returns the result
|
||||
/// in 'searchtri'. The origin of 'searchtri' does not change, even though
|
||||
/// the triangle returned may differ from the one passed in. This routine
|
||||
/// is used to find the direction to move in to get from one point to
|
||||
/// another.
|
||||
/// </remarks>
|
||||
private FindDirectionResult FindDirection(ref Otri searchtri, Vertex searchpoint)
|
||||
{
|
||||
Otri checktri = default(Otri);
|
||||
Vertex startvertex;
|
||||
Vertex leftvertex, rightvertex;
|
||||
double leftccw, rightccw;
|
||||
bool leftflag, rightflag;
|
||||
|
||||
startvertex = searchtri.Org();
|
||||
rightvertex = searchtri.Dest();
|
||||
leftvertex = searchtri.Apex();
|
||||
// Is 'searchpoint' to the left?
|
||||
leftccw = Primitives.CounterClockwise(searchpoint, startvertex, leftvertex);
|
||||
leftflag = leftccw > 0.0;
|
||||
// Is 'searchpoint' to the right?
|
||||
rightccw = Primitives.CounterClockwise(startvertex, searchpoint, rightvertex);
|
||||
rightflag = rightccw > 0.0;
|
||||
if (leftflag && rightflag)
|
||||
{
|
||||
// 'searchtri' faces directly away from 'searchpoint'. We could go left
|
||||
// or right. Ask whether it's a triangle or a boundary on the left.
|
||||
searchtri.Onext(ref checktri);
|
||||
if (checktri.triangle == Mesh.dummytri)
|
||||
{
|
||||
leftflag = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
rightflag = false;
|
||||
}
|
||||
}
|
||||
while (leftflag)
|
||||
{
|
||||
// Turn left until satisfied.
|
||||
searchtri.OnextSelf();
|
||||
if (searchtri.triangle == Mesh.dummytri)
|
||||
{
|
||||
logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().1");
|
||||
throw new Exception("Unable to find a triangle on path.");
|
||||
}
|
||||
leftvertex = searchtri.Apex();
|
||||
rightccw = leftccw;
|
||||
leftccw = Primitives.CounterClockwise(searchpoint, startvertex, leftvertex);
|
||||
leftflag = leftccw > 0.0;
|
||||
}
|
||||
while (rightflag)
|
||||
{
|
||||
// Turn right until satisfied.
|
||||
searchtri.OprevSelf();
|
||||
if (searchtri.triangle == Mesh.dummytri)
|
||||
{
|
||||
logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().2");
|
||||
throw new Exception("Unable to find a triangle on path.");
|
||||
}
|
||||
rightvertex = searchtri.Dest();
|
||||
leftccw = rightccw;
|
||||
rightccw = Primitives.CounterClockwise(startvertex, searchpoint, rightvertex);
|
||||
rightflag = rightccw > 0.0;
|
||||
}
|
||||
if (leftccw == 0.0)
|
||||
{
|
||||
return FindDirectionResult.Leftcollinear;
|
||||
}
|
||||
else if (rightccw == 0.0)
|
||||
{
|
||||
return FindDirectionResult.Rightcollinear;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FindDirectionResult.Within;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the intersection of an existing segment and a segment that is being
|
||||
/// inserted. Insert a vertex at the intersection, splitting an existing subsegment.
|
||||
/// </summary>
|
||||
/// <param name="splittri"></param>
|
||||
/// <param name="splitsubseg"></param>
|
||||
/// <param name="endpoint2"></param>
|
||||
/// <remarks>
|
||||
/// The segment being inserted connects the apex of splittri to endpoint2.
|
||||
/// splitsubseg is the subsegment being split, and MUST adjoin splittri.
|
||||
/// Hence, endpoints of the subsegment being split are the origin and
|
||||
/// destination of splittri.
|
||||
///
|
||||
/// On completion, splittri is a handle having the newly inserted
|
||||
/// intersection point as its origin, and endpoint1 as its destination.
|
||||
/// </remarks>
|
||||
private void SegmentIntersection(ref Otri splittri, ref Osub splitsubseg, Vertex endpoint2)
|
||||
{
|
||||
Osub opposubseg = default(Osub);
|
||||
Vertex endpoint1;
|
||||
Vertex torg, tdest;
|
||||
Vertex leftvertex, rightvertex;
|
||||
Vertex newvertex;
|
||||
InsertVertexResult success;
|
||||
|
||||
double ex, ey;
|
||||
double tx, ty;
|
||||
double etx, ety;
|
||||
double split, denom;
|
||||
|
||||
// Find the other three segment endpoints.
|
||||
endpoint1 = splittri.Apex();
|
||||
torg = splittri.Org();
|
||||
tdest = splittri.Dest();
|
||||
// Segment intersection formulae; see the Antonio reference.
|
||||
tx = tdest.x - torg.x;
|
||||
ty = tdest.y - torg.y;
|
||||
ex = endpoint2.x - endpoint1.x;
|
||||
ey = endpoint2.y - endpoint1.y;
|
||||
etx = torg.x - endpoint2.x;
|
||||
ety = torg.y - endpoint2.y;
|
||||
denom = ty * ex - tx * ey;
|
||||
if (denom == 0.0)
|
||||
{
|
||||
logger.Error("Attempt to find intersection of parallel segments.",
|
||||
"Mesh.SegmentIntersection()");
|
||||
throw new Exception("Attempt to find intersection of parallel segments.");
|
||||
}
|
||||
split = (ey * etx - ex * ety) / denom;
|
||||
|
||||
// Create the new vertex.
|
||||
newvertex = new Vertex(
|
||||
torg.x + split * (tdest.x - torg.x),
|
||||
torg.y + split * (tdest.y - torg.y),
|
||||
splitsubseg.seg.boundary,
|
||||
this.nextras);
|
||||
|
||||
newvertex.hash = this.hash_vtx++;
|
||||
newvertex.id = newvertex.hash;
|
||||
|
||||
// Interpolate its attributes.
|
||||
for (int i = 0; i < nextras; i++)
|
||||
{
|
||||
newvertex.attributes[i] = torg.attributes[i] + split * (tdest.attributes[i] - torg.attributes[i]);
|
||||
}
|
||||
|
||||
vertices.Add(newvertex.hash, newvertex);
|
||||
|
||||
// Insert the intersection vertex. This should always succeed.
|
||||
success = InsertVertex(newvertex, ref splittri, ref splitsubseg, false, false);
|
||||
if (success != InsertVertexResult.Successful)
|
||||
{
|
||||
logger.Error("Failure to split a segment.", "Mesh.SegmentIntersection()");
|
||||
throw new Exception("Failure to split a segment.");
|
||||
}
|
||||
// Record a triangle whose origin is the new vertex.
|
||||
newvertex.tri = splittri;
|
||||
if (steinerleft > 0)
|
||||
{
|
||||
steinerleft--;
|
||||
}
|
||||
|
||||
// Divide the segment into two, and correct the segment endpoints.
|
||||
splitsubseg.SymSelf();
|
||||
splitsubseg.Pivot(ref opposubseg);
|
||||
splitsubseg.Dissolve();
|
||||
opposubseg.Dissolve();
|
||||
do
|
||||
{
|
||||
splitsubseg.SetSegOrg(newvertex);
|
||||
splitsubseg.NextSelf();
|
||||
} while (splitsubseg.seg != Mesh.dummysub);
|
||||
do
|
||||
{
|
||||
opposubseg.SetSegOrg(newvertex);
|
||||
opposubseg.NextSelf();
|
||||
} while (opposubseg.seg != Mesh.dummysub);
|
||||
|
||||
// Inserting the vertex may have caused edge flips. We wish to rediscover
|
||||
// the edge connecting endpoint1 to the new intersection vertex.
|
||||
FindDirection(ref splittri, endpoint1);
|
||||
|
||||
rightvertex = splittri.Dest();
|
||||
leftvertex = splittri.Apex();
|
||||
if ((leftvertex.x == endpoint1.x) && (leftvertex.y == endpoint1.y))
|
||||
{
|
||||
splittri.OnextSelf();
|
||||
}
|
||||
else if ((rightvertex.x != endpoint1.x) || (rightvertex.y != endpoint1.y))
|
||||
{
|
||||
logger.Error("Topological inconsistency after splitting a segment.", "Mesh.SegmentIntersection()");
|
||||
throw new Exception("Topological inconsistency after splitting a segment.");
|
||||
}
|
||||
// 'splittri' should have destination endpoint1.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scout the first triangle on the path from one endpoint to another, and check
|
||||
/// for completion (reaching the second endpoint), a collinear vertex, or the
|
||||
/// intersection of two segments.
|
||||
/// </summary>
|
||||
/// <param name="searchtri"></param>
|
||||
/// <param name="endpoint2"></param>
|
||||
/// <param name="newmark"></param>
|
||||
/// <returns>Returns true if the entire segment is successfully inserted, and false
|
||||
/// if the job must be finished by ConstrainedEdge().</returns>
|
||||
/// <remarks>
|
||||
/// If the first triangle on the path has the second endpoint as its
|
||||
/// destination or apex, a subsegment is inserted and the job is done.
|
||||
///
|
||||
/// If the first triangle on the path has a destination or apex that lies on
|
||||
/// the segment, a subsegment is inserted connecting the first endpoint to
|
||||
/// the collinear vertex, and the search is continued from the collinear
|
||||
/// vertex.
|
||||
///
|
||||
/// If the first triangle on the path has a subsegment opposite its origin,
|
||||
/// then there is a segment that intersects the segment being inserted.
|
||||
/// Their intersection vertex is inserted, splitting the subsegment.
|
||||
/// </remarks>
|
||||
private bool ScoutSegment(ref Otri searchtri, Vertex endpoint2, int newmark)
|
||||
{
|
||||
Otri crosstri = default(Otri);
|
||||
Osub crosssubseg = default(Osub);
|
||||
Vertex leftvertex, rightvertex;
|
||||
FindDirectionResult collinear;
|
||||
|
||||
collinear = FindDirection(ref searchtri, endpoint2);
|
||||
rightvertex = searchtri.Dest();
|
||||
leftvertex = searchtri.Apex();
|
||||
if (((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y)) ||
|
||||
((rightvertex.x == endpoint2.x) && (rightvertex.y == endpoint2.y)))
|
||||
{
|
||||
// The segment is already an edge in the mesh.
|
||||
if ((leftvertex.x == endpoint2.x) && (leftvertex.y == endpoint2.y))
|
||||
{
|
||||
searchtri.LprevSelf();
|
||||
}
|
||||
// Insert a subsegment, if there isn't already one there.
|
||||
InsertSubseg(ref searchtri, newmark);
|
||||
return true;
|
||||
}
|
||||
else if (collinear == FindDirectionResult.Leftcollinear)
|
||||
{
|
||||
// We've collided with a vertex between the segment's endpoints.
|
||||
// Make the collinear vertex be the triangle's origin.
|
||||
searchtri.LprevSelf();
|
||||
InsertSubseg(ref searchtri, newmark);
|
||||
// Insert the remainder of the segment.
|
||||
return ScoutSegment(ref searchtri, endpoint2, newmark);
|
||||
}
|
||||
else if (collinear == FindDirectionResult.Rightcollinear)
|
||||
{
|
||||
// We've collided with a vertex between the segment's endpoints.
|
||||
InsertSubseg(ref searchtri, newmark);
|
||||
// Make the collinear vertex be the triangle's origin.
|
||||
searchtri.LnextSelf();
|
||||
// Insert the remainder of the segment.
|
||||
return ScoutSegment(ref searchtri, endpoint2, newmark);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchtri.Lnext(ref crosstri);
|
||||
crosstri.SegPivot(ref crosssubseg);
|
||||
// Check for a crossing segment.
|
||||
if (crosssubseg.seg == Mesh.dummysub)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert a vertex at the intersection.
|
||||
SegmentIntersection(ref crosstri, ref crosssubseg, endpoint2);
|
||||
crosstri.Copy(ref searchtri);
|
||||
InsertSubseg(ref searchtri, newmark);
|
||||
// Insert the remainder of the segment.
|
||||
return ScoutSegment(ref searchtri, endpoint2, newmark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enforce the Delaunay condition at an edge, fanning out recursively from
|
||||
/// an existing vertex. Pay special attention to stacking inverted triangles.
|
||||
/// </summary>
|
||||
/// <param name="fixuptri"></param>
|
||||
/// <param name="leftside">Indicates whether or not fixuptri is to the left of
|
||||
/// the segment being inserted. (Imagine that the segment is pointing up from
|
||||
/// endpoint1 to endpoint2.)</param>
|
||||
/// <remarks>
|
||||
/// This is a support routine for inserting segments into a constrained
|
||||
/// Delaunay triangulation.
|
||||
///
|
||||
/// The origin of fixuptri is treated as if it has just been inserted, and
|
||||
/// the local Delaunay condition needs to be enforced. It is only enforced
|
||||
/// in one sector, however, that being the angular range defined by
|
||||
/// fixuptri.
|
||||
///
|
||||
/// This routine also needs to make decisions regarding the "stacking" of
|
||||
/// triangles. (Read the description of ConstrainedEdge() below before
|
||||
/// reading on here, so you understand the algorithm.) If the position of
|
||||
/// the new vertex (the origin of fixuptri) indicates that the vertex before
|
||||
/// it on the polygon is a reflex vertex, then "stack" the triangle by
|
||||
/// doing nothing. (fixuptri is an inverted triangle, which is how stacked
|
||||
/// triangles are identified.)
|
||||
///
|
||||
/// Otherwise, check whether the vertex before that was a reflex vertex.
|
||||
/// If so, perform an edge flip, thereby eliminating an inverted triangle
|
||||
/// (popping it off the stack). The edge flip may result in the creation
|
||||
/// of a new inverted triangle, depending on whether or not the new vertex
|
||||
/// is visible to the vertex three edges behind on the polygon.
|
||||
///
|
||||
/// If neither of the two vertices behind the new vertex are reflex
|
||||
/// vertices, fixuptri and fartri, the triangle opposite it, are not
|
||||
/// inverted; hence, ensure that the edge between them is locally Delaunay.
|
||||
/// </remarks>
|
||||
private void DelaunayFixup(ref Otri fixuptri, bool leftside)
|
||||
{
|
||||
Otri neartri = default(Otri);
|
||||
Otri fartri = default(Otri);
|
||||
Osub faredge = default(Osub);
|
||||
Vertex nearvertex, leftvertex, rightvertex, farvertex;
|
||||
|
||||
fixuptri.Lnext(ref neartri);
|
||||
neartri.Sym(ref fartri);
|
||||
// Check if the edge opposite the origin of fixuptri can be flipped.
|
||||
if (fartri.triangle == Mesh.dummytri)
|
||||
{
|
||||
return;
|
||||
}
|
||||
neartri.SegPivot(ref faredge);
|
||||
if (faredge.seg != Mesh.dummysub)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Find all the relevant vertices.
|
||||
nearvertex = neartri.Apex();
|
||||
leftvertex = neartri.Org();
|
||||
rightvertex = neartri.Dest();
|
||||
farvertex = fartri.Apex();
|
||||
// Check whether the previous polygon vertex is a reflex vertex.
|
||||
if (leftside)
|
||||
{
|
||||
if (Primitives.CounterClockwise(nearvertex, leftvertex, farvertex) <= 0.0)
|
||||
{
|
||||
// leftvertex is a reflex vertex too. Nothing can
|
||||
// be done until a convex section is found.
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Primitives.CounterClockwise(farvertex, rightvertex, nearvertex) <= 0.0)
|
||||
{
|
||||
// rightvertex is a reflex vertex too. Nothing can
|
||||
// be done until a convex section is found.
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Primitives.CounterClockwise(rightvertex, leftvertex, farvertex) > 0.0)
|
||||
{
|
||||
// fartri is not an inverted triangle, and farvertex is not a reflex
|
||||
// vertex. As there are no reflex vertices, fixuptri isn't an
|
||||
// inverted triangle, either. Hence, test the edge between the
|
||||
// triangles to ensure it is locally Delaunay.
|
||||
if (Primitives.InCircle(leftvertex, farvertex, rightvertex, nearvertex) <= 0.0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Not locally Delaunay; go on to an edge flip.
|
||||
}
|
||||
// else fartri is inverted; remove it from the stack by flipping.
|
||||
Flip(ref neartri);
|
||||
fixuptri.LprevSelf(); // Restore the origin of fixuptri after the flip.
|
||||
// Recursively process the two triangles that result from the flip.
|
||||
DelaunayFixup(ref fixuptri, leftside);
|
||||
DelaunayFixup(ref fartri, leftside);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force a segment into a constrained Delaunay triangulation by deleting the
|
||||
/// triangles it intersects, and triangulating the polygons that form on each
|
||||
/// side of it.
|
||||
/// </summary>
|
||||
/// <param name="starttri"></param>
|
||||
/// <param name="endpoint2"></param>
|
||||
/// <param name="newmark"></param>
|
||||
/// <remarks>
|
||||
/// Generates a single subsegment connecting 'endpoint1' to 'endpoint2'.
|
||||
/// The triangle 'starttri' has 'endpoint1' as its origin. 'newmark' is the
|
||||
/// boundary marker of the segment.
|
||||
///
|
||||
/// To insert a segment, every triangle whose interior intersects the
|
||||
/// segment is deleted. The union of these deleted triangles is a polygon
|
||||
/// (which is not necessarily monotone, but is close enough), which is
|
||||
/// divided into two polygons by the new segment. This routine's task is
|
||||
/// to generate the Delaunay triangulation of these two polygons.
|
||||
///
|
||||
/// You might think of this routine's behavior as a two-step process. The
|
||||
/// first step is to walk from endpoint1 to endpoint2, flipping each edge
|
||||
/// encountered. This step creates a fan of edges connected to endpoint1,
|
||||
/// including the desired edge to endpoint2. The second step enforces the
|
||||
/// Delaunay condition on each side of the segment in an incremental manner:
|
||||
/// proceeding along the polygon from endpoint1 to endpoint2 (this is done
|
||||
/// independently on each side of the segment), each vertex is "enforced"
|
||||
/// as if it had just been inserted, but affecting only the previous
|
||||
/// vertices. The result is the same as if the vertices had been inserted
|
||||
/// in the order they appear on the polygon, so the result is Delaunay.
|
||||
///
|
||||
/// In truth, ConstrainedEdge() interleaves these two steps. The procedure
|
||||
/// walks from endpoint1 to endpoint2, and each time an edge is encountered
|
||||
/// and flipped, the newly exposed vertex (at the far end of the flipped
|
||||
/// edge) is "enforced" upon the previously flipped edges, usually affecting
|
||||
/// only one side of the polygon (depending upon which side of the segment
|
||||
/// the vertex falls on).
|
||||
///
|
||||
/// The algorithm is complicated by the need to handle polygons that are not
|
||||
/// convex. Although the polygon is not necessarily monotone, it can be
|
||||
/// triangulated in a manner similar to the stack-based algorithms for
|
||||
/// monotone polygons. For each reflex vertex (local concavity) of the
|
||||
/// polygon, there will be an inverted triangle formed by one of the edge
|
||||
/// flips. (An inverted triangle is one with negative area - that is, its
|
||||
/// vertices are arranged in clockwise order - and is best thought of as a
|
||||
/// wrinkle in the fabric of the mesh.) Each inverted triangle can be
|
||||
/// thought of as a reflex vertex pushed on the stack, waiting to be fixed
|
||||
/// later.
|
||||
///
|
||||
/// A reflex vertex is popped from the stack when a vertex is inserted that
|
||||
/// is visible to the reflex vertex. (However, if the vertex behind the
|
||||
/// reflex vertex is not visible to the reflex vertex, a new inverted
|
||||
/// triangle will take its place on the stack.) These details are handled
|
||||
/// by the DelaunayFixup() routine above.
|
||||
/// </remarks>
|
||||
private void ConstrainedEdge(ref Otri starttri, Vertex endpoint2, int newmark)
|
||||
{
|
||||
Otri fixuptri = default(Otri), fixuptri2 = default(Otri);
|
||||
Osub crosssubseg = default(Osub);
|
||||
Vertex endpoint1;
|
||||
Vertex farvertex;
|
||||
double area;
|
||||
bool collision;
|
||||
bool done;
|
||||
|
||||
endpoint1 = starttri.Org();
|
||||
starttri.Lnext(ref fixuptri);
|
||||
Flip(ref fixuptri);
|
||||
// 'collision' indicates whether we have found a vertex directly
|
||||
// between endpoint1 and endpoint2.
|
||||
collision = false;
|
||||
done = false;
|
||||
do
|
||||
{
|
||||
farvertex = fixuptri.Org();
|
||||
// 'farvertex' is the extreme point of the polygon we are "digging"
|
||||
// to get from endpoint1 to endpoint2.
|
||||
if ((farvertex.x == endpoint2.x) && (farvertex.y == endpoint2.y))
|
||||
{
|
||||
fixuptri.Oprev(ref fixuptri2);
|
||||
// Enforce the Delaunay condition around endpoint2.
|
||||
DelaunayFixup(ref fixuptri, false);
|
||||
DelaunayFixup(ref fixuptri2, true);
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check whether farvertex is to the left or right of the segment being
|
||||
// inserted, to decide which edge of fixuptri to dig through next.
|
||||
area = Primitives.CounterClockwise(endpoint1, endpoint2, farvertex);
|
||||
if (area == 0.0)
|
||||
{
|
||||
// We've collided with a vertex between endpoint1 and endpoint2.
|
||||
collision = true;
|
||||
fixuptri.Oprev(ref fixuptri2);
|
||||
// Enforce the Delaunay condition around farvertex.
|
||||
DelaunayFixup(ref fixuptri, false);
|
||||
DelaunayFixup(ref fixuptri2, true);
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (area > 0.0)
|
||||
{
|
||||
// farvertex is to the left of the segment.
|
||||
fixuptri.Oprev(ref fixuptri2);
|
||||
// Enforce the Delaunay condition around farvertex, on the
|
||||
// left side of the segment only.
|
||||
DelaunayFixup(ref fixuptri2, true);
|
||||
// Flip the edge that crosses the segment. After the edge is
|
||||
// flipped, one of its endpoints is the fan vertex, and the
|
||||
// destination of fixuptri is the fan vertex.
|
||||
fixuptri.LprevSelf();
|
||||
}
|
||||
else
|
||||
{
|
||||
// farvertex is to the right of the segment.
|
||||
DelaunayFixup(ref fixuptri, false);
|
||||
// Flip the edge that crosses the segment. After the edge is
|
||||
// flipped, one of its endpoints is the fan vertex, and the
|
||||
// destination of fixuptri is the fan vertex.
|
||||
fixuptri.OprevSelf();
|
||||
}
|
||||
// Check for two intersecting segments.
|
||||
fixuptri.SegPivot(ref crosssubseg);
|
||||
if (crosssubseg.seg == Mesh.dummysub)
|
||||
{
|
||||
Flip(ref fixuptri); // May create inverted triangle at left.
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've collided with a segment between endpoint1 and endpoint2.
|
||||
collision = true;
|
||||
// Insert a vertex at the intersection.
|
||||
SegmentIntersection(ref fixuptri, ref crosssubseg, endpoint2);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!done);
|
||||
// Insert a subsegment to make the segment permanent.
|
||||
InsertSubseg(ref fixuptri, newmark);
|
||||
// If there was a collision with an interceding vertex, install another
|
||||
// segment connecting that vertex with endpoint2.
|
||||
if (collision)
|
||||
{
|
||||
// Insert the remainder of the segment.
|
||||
if (!ScoutSegment(ref fixuptri, endpoint2, newmark))
|
||||
{
|
||||
ConstrainedEdge(ref fixuptri, endpoint2, newmark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insert a PSLG segment into a triangulation.
|
||||
/// </summary>
|
||||
/// <param name="endpoint1"></param>
|
||||
/// <param name="endpoint2"></param>
|
||||
/// <param name="newmark"></param>
|
||||
private void InsertSegment(Vertex endpoint1, Vertex endpoint2, int newmark)
|
||||
{
|
||||
Otri searchtri1 = default(Otri), searchtri2 = default(Otri);
|
||||
Vertex checkvertex = null;
|
||||
|
||||
// Find a triangle whose origin is the segment's first endpoint.
|
||||
searchtri1 = endpoint1.tri;
|
||||
if (searchtri1.triangle != null)
|
||||
{
|
||||
checkvertex = searchtri1.Org();
|
||||
}
|
||||
|
||||
if (checkvertex != endpoint1)
|
||||
{
|
||||
// Find a boundary triangle to search from.
|
||||
searchtri1.triangle = Mesh.dummytri;
|
||||
searchtri1.orient = 0;
|
||||
searchtri1.SymSelf();
|
||||
// Search for the segment's first endpoint by point location.
|
||||
if (locator.Locate(endpoint1, ref searchtri1) != LocateResult.OnVertex)
|
||||
{
|
||||
logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().1");
|
||||
throw new Exception("Unable to locate PSLG vertex in triangulation.");
|
||||
}
|
||||
}
|
||||
// Remember this triangle to improve subsequent point location.
|
||||
locator.Update(ref searchtri1);
|
||||
|
||||
// Scout the beginnings of a path from the first endpoint
|
||||
// toward the second.
|
||||
if (ScoutSegment(ref searchtri1, endpoint2, newmark))
|
||||
{
|
||||
// The segment was easily inserted.
|
||||
return;
|
||||
}
|
||||
// The first endpoint may have changed if a collision with an intervening
|
||||
// vertex on the segment occurred.
|
||||
endpoint1 = searchtri1.Org();
|
||||
|
||||
// Find a triangle whose origin is the segment's second endpoint.
|
||||
checkvertex = null;
|
||||
searchtri2 = endpoint2.tri;
|
||||
if (searchtri2.triangle != null)
|
||||
{
|
||||
checkvertex = searchtri2.Org();
|
||||
}
|
||||
if (checkvertex != endpoint2)
|
||||
{
|
||||
// Find a boundary triangle to search from.
|
||||
searchtri2.triangle = Mesh.dummytri;
|
||||
searchtri2.orient = 0;
|
||||
searchtri2.SymSelf();
|
||||
// Search for the segment's second endpoint by point location.
|
||||
if (locator.Locate(endpoint2, ref searchtri2) != LocateResult.OnVertex)
|
||||
{
|
||||
logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().2");
|
||||
throw new Exception("Unable to locate PSLG vertex in triangulation.");
|
||||
}
|
||||
}
|
||||
// Remember this triangle to improve subsequent point location.
|
||||
locator.Update(ref searchtri2);
|
||||
// Scout the beginnings of a path from the second endpoint
|
||||
// toward the first.
|
||||
if (ScoutSegment(ref searchtri2, endpoint1, newmark))
|
||||
{
|
||||
// The segment was easily inserted.
|
||||
return;
|
||||
}
|
||||
// The second endpoint may have changed if a collision with an intervening
|
||||
// vertex on the segment occurred.
|
||||
endpoint2 = searchtri2.Org();
|
||||
|
||||
// Insert the segment directly into the triangulation.
|
||||
ConstrainedEdge(ref searchtri1, endpoint2, newmark);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cover the convex hull of a triangulation with subsegments.
|
||||
/// </summary>
|
||||
private void MarkHull()
|
||||
{
|
||||
Otri hulltri = default(Otri);
|
||||
Otri nexttri = default(Otri);
|
||||
Otri starttri = default(Otri);
|
||||
|
||||
// Find a triangle handle on the hull.
|
||||
hulltri.triangle = Mesh.dummytri;
|
||||
hulltri.orient = 0;
|
||||
hulltri.SymSelf();
|
||||
// Remember where we started so we know when to stop.
|
||||
hulltri.Copy(ref starttri);
|
||||
// Go once counterclockwise around the convex hull.
|
||||
do
|
||||
{
|
||||
// Create a subsegment if there isn't already one here.
|
||||
InsertSubseg(ref hulltri, 1);
|
||||
// To find the next hull edge, go clockwise around the next vertex.
|
||||
hulltri.LnextSelf();
|
||||
hulltri.Oprev(ref nexttri);
|
||||
while (nexttri.triangle != Mesh.dummytri)
|
||||
{
|
||||
nexttri.Copy(ref hulltri);
|
||||
hulltri.Oprev(ref nexttri);
|
||||
}
|
||||
} while (!hulltri.Equal(starttri));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the segments of a triangulation, including PSLG segments and edges
|
||||
/// on the convex hull.
|
||||
/// </summary>
|
||||
/// <param name="segmentlist"></param>
|
||||
/// <param name="segmentmarkerlist"></param>
|
||||
/// <param name="numberofsegments"></param>
|
||||
private void FormSkeleton(InputGeometry input)
|
||||
{
|
||||
Vertex endpoint1, endpoint2;
|
||||
int end1, end2;
|
||||
int boundmarker;
|
||||
|
||||
this.insegments = 0;
|
||||
|
||||
if (behavior.Poly)
|
||||
{
|
||||
// If the input vertices are collinear, there is no triangulation,
|
||||
// so don't try to insert segments.
|
||||
if (triangles.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If segments are to be inserted, compute a mapping
|
||||
// from vertices to triangles.
|
||||
if (input.HasSegments)
|
||||
{
|
||||
MakeVertexMap();
|
||||
}
|
||||
|
||||
boundmarker = 0;
|
||||
|
||||
// Read and insert the segments.
|
||||
foreach (var seg in input.segments)
|
||||
{
|
||||
this.insegments++;
|
||||
|
||||
end1 = seg.P0;
|
||||
end2 = seg.P1;
|
||||
boundmarker = seg.Boundary;
|
||||
|
||||
if ((end1 < 0) || (end1 >= invertices))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
logger.Warning("Invalid first endpoint of segment.", "Mesh.FormSkeleton().1");
|
||||
}
|
||||
}
|
||||
else if ((end2 < 0) || (end2 >= invertices))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
logger.Warning("Invalid second endpoint of segment.", "Mesh.FormSkeleton().2");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Is using the vertex ID reliable???
|
||||
// It should be. The ID gets appropriately set in TransferNodes().
|
||||
|
||||
// Find the vertices numbered 'end1' and 'end2'.
|
||||
endpoint1 = vertices[end1];
|
||||
endpoint2 = vertices[end2];
|
||||
if ((endpoint1.x == endpoint2.x) && (endpoint1.y == endpoint2.y))
|
||||
{
|
||||
if (Behavior.Verbose)
|
||||
{
|
||||
logger.Warning("Endpoints of segment (IDs " + end1 + "/" + end2 + ") are coincident.",
|
||||
"Mesh.FormSkeleton()");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
InsertSegment(endpoint1, endpoint2, boundmarker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (behavior.Convex || !behavior.Poly)
|
||||
{
|
||||
// Enclose the convex hull with subsegments.
|
||||
MarkHull();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dealloc
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="MeshValidator.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
|
||||
{
|
||||
using System;
|
||||
using TriangleNet.Data;
|
||||
using TriangleNet.Log;
|
||||
|
||||
public static class MeshValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Test the mesh for topological consistency.
|
||||
/// </summary>
|
||||
public static bool IsConsistent(Mesh mesh)
|
||||
{
|
||||
Otri tri = default(Otri);
|
||||
Otri oppotri = default(Otri), oppooppotri = default(Otri);
|
||||
Vertex triorg, tridest, triapex;
|
||||
Vertex oppoorg, oppodest;
|
||||
int horrors;
|
||||
bool saveexact;
|
||||
|
||||
var logger = SimpleLog.Instance;
|
||||
|
||||
// Temporarily turn on exact arithmetic if it's off.
|
||||
saveexact = Behavior.NoExact;
|
||||
Behavior.NoExact = false;
|
||||
horrors = 0;
|
||||
|
||||
// Run through the list of triangles, checking each one.
|
||||
foreach (var t in mesh.triangles.Values)
|
||||
{
|
||||
tri.triangle = t;
|
||||
|
||||
// Check all three edges of the triangle.
|
||||
for (tri.orient = 0; tri.orient < 3; tri.orient++)
|
||||
{
|
||||
triorg = tri.Org();
|
||||
tridest = tri.Dest();
|
||||
if (tri.orient == 0)
|
||||
{ // Only test for inversion once.
|
||||
// Test if the triangle is flat or inverted.
|
||||
triapex = tri.Apex();
|
||||
if (Primitives.CounterClockwise(triorg, tridest, triapex) <= 0.0)
|
||||
{
|
||||
logger.Warning("Triangle is flat or inverted.",
|
||||
"Quality.CheckMesh()");
|
||||
horrors++;
|
||||
}
|
||||
}
|
||||
// Find the neighboring triangle on this edge.
|
||||
tri.Sym(ref oppotri);
|
||||
if (oppotri.triangle != Mesh.dummytri)
|
||||
{
|
||||
// Check that the triangle's neighbor knows it's a neighbor.
|
||||
oppotri.Sym(ref oppooppotri);
|
||||
if ((tri.triangle != oppooppotri.triangle) || (tri.orient != oppooppotri.orient))
|
||||
{
|
||||
if (tri.triangle == oppooppotri.triangle)
|
||||
{
|
||||
logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)",
|
||||
"Quality.CheckMesh()");
|
||||
}
|
||||
|
||||
horrors++;
|
||||
}
|
||||
// Check that both triangles agree on the identities
|
||||
// of their shared vertices.
|
||||
oppoorg = oppotri.Org();
|
||||
oppodest = oppotri.Dest();
|
||||
if ((triorg != oppodest) || (tridest != oppoorg))
|
||||
{
|
||||
logger.Warning("Mismatched edge coordinates between two triangles.",
|
||||
"Quality.CheckMesh()");
|
||||
|
||||
horrors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for unconnected vertices
|
||||
mesh.MakeVertexMap();
|
||||
foreach (var v in mesh.vertices.Values)
|
||||
{
|
||||
if (v.tri.triangle == null)
|
||||
{
|
||||
logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)",
|
||||
"Quality.CheckMesh()");
|
||||
}
|
||||
}
|
||||
|
||||
if (horrors == 0) // && Behavior.Verbose
|
||||
{
|
||||
logger.Info("Mesh topology appears to be consistent.");
|
||||
}
|
||||
|
||||
// Restore the status of exact arithmetic.
|
||||
Behavior.NoExact = saveexact;
|
||||
|
||||
return (horrors == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that the mesh is (constrained) Delaunay.
|
||||
/// </summary>
|
||||
public static bool IsDelaunay(Mesh mesh)
|
||||
{
|
||||
Otri loop = default(Otri);
|
||||
Otri oppotri = default(Otri);
|
||||
Osub opposubseg = default(Osub);
|
||||
Vertex triorg, tridest, triapex;
|
||||
Vertex oppoapex;
|
||||
bool shouldbedelaunay;
|
||||
int horrors;
|
||||
bool saveexact;
|
||||
|
||||
var logger = SimpleLog.Instance;
|
||||
|
||||
// Temporarily turn on exact arithmetic if it's off.
|
||||
saveexact = Behavior.NoExact;
|
||||
Behavior.NoExact = false;
|
||||
horrors = 0;
|
||||
|
||||
// Run through the list of triangles, checking each one.
|
||||
foreach (var tri in mesh.triangles.Values)
|
||||
{
|
||||
loop.triangle = tri;
|
||||
|
||||
// Check all three edges of the triangle.
|
||||
for (loop.orient = 0; loop.orient < 3;
|
||||
loop.orient++)
|
||||
{
|
||||
triorg = loop.Org();
|
||||
tridest = loop.Dest();
|
||||
triapex = loop.Apex();
|
||||
loop.Sym(ref oppotri);
|
||||
oppoapex = oppotri.Apex();
|
||||
// Only test that the edge is locally Delaunay if there is an
|
||||
// adjoining triangle whose pointer is larger (to ensure that
|
||||
// each pair isn't tested twice).
|
||||
shouldbedelaunay = (oppotri.triangle != Mesh.dummytri) &&
|
||||
!Otri.IsDead(oppotri.triangle) && loop.triangle.id < oppotri.triangle.id &&
|
||||
(triorg != mesh.infvertex1) && (triorg != mesh.infvertex2) &&
|
||||
(triorg != mesh.infvertex3) &&
|
||||
(tridest != mesh.infvertex1) && (tridest != mesh.infvertex2) &&
|
||||
(tridest != mesh.infvertex3) &&
|
||||
(triapex != mesh.infvertex1) && (triapex != mesh.infvertex2) &&
|
||||
(triapex != mesh.infvertex3) &&
|
||||
(oppoapex != mesh.infvertex1) && (oppoapex != mesh.infvertex2) &&
|
||||
(oppoapex != mesh.infvertex3);
|
||||
if (mesh.checksegments && shouldbedelaunay)
|
||||
{
|
||||
// If a subsegment separates the triangles, then the edge is
|
||||
// constrained, so no local Delaunay test should be done.
|
||||
loop.SegPivot(ref opposubseg);
|
||||
if (opposubseg.seg != Mesh.dummysub)
|
||||
{
|
||||
shouldbedelaunay = false;
|
||||
}
|
||||
}
|
||||
if (shouldbedelaunay)
|
||||
{
|
||||
if (Primitives.NonRegular(triorg, tridest, triapex, oppoapex) > 0.0)
|
||||
{
|
||||
logger.Warning(String.Format("Non-regular pair of triangles found (IDs {0}/{1}).",
|
||||
loop.triangle.id, oppotri.triangle.id), "Quality.CheckDelaunay()");
|
||||
horrors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (horrors == 0) // && Behavior.Verbose
|
||||
{
|
||||
logger.Info("Mesh is Delaunay.");
|
||||
}
|
||||
|
||||
// Restore the status of exact arithmetic.
|
||||
Behavior.NoExact = saveexact;
|
||||
|
||||
return (horrors == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,6 @@ namespace TriangleNet
|
||||
|
||||
NewLocation newLocation;
|
||||
|
||||
// Not used at the moment
|
||||
Func<Point, Point, Point, double, bool> userTest;
|
||||
|
||||
ILog<SimpleLogItem> logger;
|
||||
|
||||
public Quality(Mesh mesh)
|
||||
@@ -54,177 +51,6 @@ namespace TriangleNet
|
||||
|
||||
#region Check
|
||||
|
||||
/// <summary>
|
||||
/// Test the mesh for topological consistency.
|
||||
/// </summary>
|
||||
public bool CheckMesh()
|
||||
{
|
||||
Otri tri = default(Otri);
|
||||
Otri oppotri = default(Otri), oppooppotri = default(Otri);
|
||||
Vertex triorg, tridest, triapex;
|
||||
Vertex oppoorg, oppodest;
|
||||
int horrors;
|
||||
bool saveexact;
|
||||
|
||||
// Temporarily turn on exact arithmetic if it's off.
|
||||
saveexact = Behavior.NoExact;
|
||||
Behavior.NoExact = false;
|
||||
horrors = 0;
|
||||
|
||||
// Run through the list of triangles, checking each one.
|
||||
foreach (var t in mesh.triangles.Values)
|
||||
{
|
||||
tri.triangle = t;
|
||||
|
||||
// Check all three edges of the triangle.
|
||||
for (tri.orient = 0; tri.orient < 3; tri.orient++)
|
||||
{
|
||||
triorg = tri.Org();
|
||||
tridest = tri.Dest();
|
||||
if (tri.orient == 0)
|
||||
{ // Only test for inversion once.
|
||||
// Test if the triangle is flat or inverted.
|
||||
triapex = tri.Apex();
|
||||
if (Primitives.CounterClockwise(triorg, tridest, triapex) <= 0.0)
|
||||
{
|
||||
logger.Warning("Triangle is flat or inverted.",
|
||||
"Quality.CheckMesh()");
|
||||
horrors++;
|
||||
}
|
||||
}
|
||||
// Find the neighboring triangle on this edge.
|
||||
tri.Sym(ref oppotri);
|
||||
if (oppotri.triangle != Mesh.dummytri)
|
||||
{
|
||||
// Check that the triangle's neighbor knows it's a neighbor.
|
||||
oppotri.Sym(ref oppooppotri);
|
||||
if ((tri.triangle != oppooppotri.triangle) || (tri.orient != oppooppotri.orient))
|
||||
{
|
||||
if (tri.triangle == oppooppotri.triangle)
|
||||
{
|
||||
logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)",
|
||||
"Quality.CheckMesh()");
|
||||
}
|
||||
|
||||
horrors++;
|
||||
}
|
||||
// Check that both triangles agree on the identities
|
||||
// of their shared vertices.
|
||||
oppoorg = oppotri.Org();
|
||||
oppodest = oppotri.Dest();
|
||||
if ((triorg != oppodest) || (tridest != oppoorg))
|
||||
{
|
||||
logger.Warning("Mismatched edge coordinates between two triangles.",
|
||||
"Quality.CheckMesh()");
|
||||
|
||||
horrors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for unconnected vertices
|
||||
mesh.MakeVertexMap();
|
||||
foreach (var v in mesh.vertices.Values)
|
||||
{
|
||||
if (v.tri.triangle == null)
|
||||
{
|
||||
logger.Warning("Vertex (ID " + v.id + ") not connected to mesh (duplicate input vertex?)",
|
||||
"Quality.CheckMesh()");
|
||||
}
|
||||
}
|
||||
|
||||
if (horrors == 0) // && Behavior.Verbose
|
||||
{
|
||||
logger.Info("Mesh topology appears to be consistent.");
|
||||
}
|
||||
|
||||
// Restore the status of exact arithmetic.
|
||||
Behavior.NoExact = saveexact;
|
||||
|
||||
return (horrors == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that the mesh is (constrained) Delaunay.
|
||||
/// </summary>
|
||||
public bool CheckDelaunay()
|
||||
{
|
||||
Otri loop = default(Otri);
|
||||
Otri oppotri = default(Otri);
|
||||
Osub opposubseg = default(Osub);
|
||||
Vertex triorg, tridest, triapex;
|
||||
Vertex oppoapex;
|
||||
bool shouldbedelaunay;
|
||||
int horrors;
|
||||
bool saveexact;
|
||||
|
||||
// Temporarily turn on exact arithmetic if it's off.
|
||||
saveexact = Behavior.NoExact;
|
||||
Behavior.NoExact = false;
|
||||
horrors = 0;
|
||||
|
||||
// Run through the list of triangles, checking each one.
|
||||
foreach (var tri in mesh.triangles.Values)
|
||||
{
|
||||
loop.triangle = tri;
|
||||
|
||||
// Check all three edges of the triangle.
|
||||
for (loop.orient = 0; loop.orient < 3;
|
||||
loop.orient++)
|
||||
{
|
||||
triorg = loop.Org();
|
||||
tridest = loop.Dest();
|
||||
triapex = loop.Apex();
|
||||
loop.Sym(ref oppotri);
|
||||
oppoapex = oppotri.Apex();
|
||||
// Only test that the edge is locally Delaunay if there is an
|
||||
// adjoining triangle whose pointer is larger (to ensure that
|
||||
// each pair isn't tested twice).
|
||||
shouldbedelaunay = (oppotri.triangle != Mesh.dummytri) &&
|
||||
!Otri.IsDead(oppotri.triangle) && loop.triangle.id < oppotri.triangle.id &&
|
||||
(triorg != mesh.infvertex1) && (triorg != mesh.infvertex2) &&
|
||||
(triorg != mesh.infvertex3) &&
|
||||
(tridest != mesh.infvertex1) && (tridest != mesh.infvertex2) &&
|
||||
(tridest != mesh.infvertex3) &&
|
||||
(triapex != mesh.infvertex1) && (triapex != mesh.infvertex2) &&
|
||||
(triapex != mesh.infvertex3) &&
|
||||
(oppoapex != mesh.infvertex1) && (oppoapex != mesh.infvertex2) &&
|
||||
(oppoapex != mesh.infvertex3);
|
||||
if (mesh.checksegments && shouldbedelaunay)
|
||||
{
|
||||
// If a subsegment separates the triangles, then the edge is
|
||||
// constrained, so no local Delaunay test should be done.
|
||||
loop.SegPivot(ref opposubseg);
|
||||
if (opposubseg.seg != Mesh.dummysub)
|
||||
{
|
||||
shouldbedelaunay = false;
|
||||
}
|
||||
}
|
||||
if (shouldbedelaunay)
|
||||
{
|
||||
if (Primitives.NonRegular(triorg, tridest, triapex, oppoapex) > 0.0)
|
||||
{
|
||||
logger.Warning(String.Format("Non-regular pair of triangles found (IDs {0}/{1}).",
|
||||
loop.triangle.id, oppotri.triangle.id), "Quality.CheckDelaunay()");
|
||||
horrors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (horrors == 0) // && Behavior.Verbose
|
||||
{
|
||||
logger.Info("Mesh is Delaunay.");
|
||||
}
|
||||
|
||||
// Restore the status of exact arithmetic.
|
||||
Behavior.NoExact = saveexact;
|
||||
|
||||
return (horrors == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check a subsegment to see if it is encroached; add it to the list if it is.
|
||||
/// </summary>
|
||||
@@ -421,7 +247,7 @@ namespace TriangleNet
|
||||
testtri.Lprev(ref tri1);
|
||||
}
|
||||
|
||||
if (behavior.VarArea || behavior.fixedArea || behavior.Usertest)
|
||||
if (behavior.VarArea || behavior.fixedArea || (behavior.UserTest != null))
|
||||
{
|
||||
// Check whether the area is larger than permitted.
|
||||
area = 0.5 * (dxod * dyda - dyod * dxda);
|
||||
@@ -441,9 +267,9 @@ namespace TriangleNet
|
||||
}
|
||||
|
||||
// Check whether the user thinks this triangle is too large.
|
||||
if (behavior.Usertest && userTest != null)
|
||||
if (behavior.UserTest != null)
|
||||
{
|
||||
if (userTest(torg, tdest, tapex, area))
|
||||
if (behavior.UserTest(testtri.triangle, area))
|
||||
{
|
||||
queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
|
||||
return;
|
||||
@@ -955,7 +781,7 @@ namespace TriangleNet
|
||||
// triangulation should be (conforming) Delaunay.
|
||||
|
||||
// Next, we worry about enforcing triangle quality.
|
||||
if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.Usertest)
|
||||
if ((behavior.MinAngle > 0.0) || behavior.VarArea || behavior.fixedArea || behavior.UserTest != null)
|
||||
{
|
||||
// TODO: Reset queue? (Or is it always empty at this point)
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<Compile Include="BadTriQueue.cs" />
|
||||
<Compile Include="Behavior.cs" />
|
||||
<Compile Include="Carver.cs" />
|
||||
<Compile Include="ConstraintMesher.cs" />
|
||||
<Compile Include="Data\BadSubseg.cs" />
|
||||
<Compile Include="Data\BadTriangle.cs" />
|
||||
<Compile Include="Data\Osub.cs" />
|
||||
@@ -73,6 +74,7 @@
|
||||
<Compile Include="Log\ILogItem.cs" />
|
||||
<Compile Include="Log\SimpleLog.cs" />
|
||||
<Compile Include="Log\SimpleLogItem.cs" />
|
||||
<Compile Include="MeshValidator.cs" />
|
||||
<Compile Include="NewLocation.cs" />
|
||||
<Compile Include="Quality.cs" />
|
||||
<Compile Include="Enums.cs" />
|
||||
|
||||
Reference in New Issue
Block a user