PO/Library/PackageCache/com.unity.2d.animation@5.0.7/Runtime/Triangle/Meshing/QualityMesher.cs

900 lines
39 KiB
C#
Raw Normal View History

2022-01-12 10:06:03 +03:00
// -----------------------------------------------------------------------
// <copyright file="QualityMesher.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 UnityEngine.U2D.Animation.TriangleNet
.Meshing
{
using System;
using System.Collections.Generic;
using Animation.TriangleNet.Geometry;
using Animation.TriangleNet.Logging;
using Animation.TriangleNet.Meshing.Data;
using Animation.TriangleNet.Tools;
using Animation.TriangleNet.Topology;
/// <summary>
/// Provides methods for mesh quality enforcement and testing.
/// </summary>
class QualityMesher
{
IPredicates predicates;
Queue<BadSubseg> badsubsegs;
BadTriQueue queue;
Mesh mesh;
Behavior behavior;
NewLocation newLocation;
ILog<LogItem> logger;
// Stores the vertices of the triangle that contains newvertex
// in SplitTriangle method.
Triangle newvertex_tri;
public QualityMesher(Mesh mesh, Configuration config)
{
logger = Log.Instance;
badsubsegs = new Queue<BadSubseg>();
queue = new BadTriQueue();
this.mesh = mesh;
this.predicates = config.Predicates();
this.behavior = mesh.behavior;
newLocation = new NewLocation(mesh, predicates);
newvertex_tri = new Triangle();
}
/// <summary>
/// Apply quality constraints to a mesh.
/// </summary>
/// <param name="quality">The quality constraints.</param>
/// <param name="delaunay">A value indicating, if the refined mesh should be Conforming Delaunay.</param>
public void Apply(QualityOptions quality, bool delaunay = false)
{
// Copy quality options
if (quality != null)
{
behavior.Quality = true;
behavior.MinAngle = quality.MinimumAngle;
behavior.MaxAngle = quality.MaximumAngle;
behavior.MaxArea = quality.MaximumArea;
behavior.UserTest = quality.UserTest;
behavior.VarArea = quality.VariableArea;
behavior.ConformingDelaunay = behavior.ConformingDelaunay || delaunay;
mesh.steinerleft = quality.SteinerPoints == 0 ? -1 : quality.SteinerPoints;
}
// TODO: remove
if (!behavior.Poly)
{
// Be careful not to allocate space for element area constraints that
// will never be assigned any value (other than the default -1.0).
behavior.VarArea = false;
}
// Ensure that no vertex can be mistaken for a triangular bounding
// box vertex in insertvertex().
mesh.infvertex1 = null;
mesh.infvertex2 = null;
mesh.infvertex3 = null;
if (behavior.useSegments)
{
mesh.checksegments = true;
}
if (behavior.Quality && mesh.triangles.Count > 0)
{
// Enforce angle and area constraints.
EnforceQuality();
}
}
/// <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.Pivot(ref neighbortri);
// Does the neighbor exist, or is this a boundary edge?
if (neighbortri.tri.id != Mesh.DUMMY)
{
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.Pivot(ref neighbortri);
// Does the neighbor exist, or is this a boundary edge?
if (neighbortri.tri.id != Mesh.DUMMY)
{
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.subseg = testsubseg;
encroachedseg.org = eorg;
encroachedseg.dest = edest;
}
else
{
encroachedseg.subseg = testsym;
encroachedseg.org = edest;
encroachedseg.dest = 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.tri.area) && (testtri.tri.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) && behavior.UserTest(testtri.tri, 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.Pivot(ref testsub);
if (testsub.seg.hash == Mesh.DUMMY)
{
// No common segment. Find a subsegment that contains 'torg'.
tri1.Copy(ref tri2);
do
{
tri1.Oprev();
tri1.Pivot(ref testsub);
}
while (testsub.seg.hash == Mesh.DUMMY);
// Find the endpoints of the containing segment.
org1 = testsub.SegOrg();
dest1 = testsub.SegDest();
// Find a subsegment that contains 'tdest'.
do
{
tri2.Dnext();
tri2.Pivot(ref testsub);
}
while (testsub.seg.hash == Mesh.DUMMY);
// 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.subseg;
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.org) && (edest == seg.dest))
{
// 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.Pivot(ref enctri);
enctri.Lnext(ref testtri);
testtri.Pivot(ref testsh);
acuteorg = testsh.seg.hash != Mesh.DUMMY;
// Is the destination shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acutedest = testsh.seg.hash != Mesh.DUMMY;
// 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.Pivot(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.tri.id != Mesh.DUMMY)
{
// Is the destination shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acutedest2 = testsh.seg.hash != Mesh.DUMMY;
acutedest = acutedest || acutedest2;
// Is the origin shared with another segment?
testtri.Lnext();
testtri.Pivot(ref testsh);
acuteorg2 = testsh.seg.hash != Mesh.DUMMY;
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.Lprev();
}
}
}
// 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.seg.boundary
#if USE_ATTRIBS
, mesh.nextras
#endif
);
newvertex.type = VertexType.SegmentVertex;
newvertex.hash = mesh.hash_vtx++;
newvertex.id = newvertex.hash;
mesh.vertices.Add(newvertex.hash, newvertex);
#if USE_ATTRIBS
// Interpolate attributes.
for (int i = 0; i < mesh.nextras; i++)
{
newvertex.attributes[i] = eorg.attributes[i]
+ split * (edest.attributes[i] - eorg.attributes[i]);
}
#endif
#if USE_Z
newvertex.z = eorg.z + split * (edest.z - eorg.z);
#endif
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 = predicates.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.Next();
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.org = 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)
{
triangleloop.tri = 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.tri) && (borg == badtri.org) &&
(bdest == badtri.dest) && (bapex == badtri.apex))
{
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 = predicates.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 (Log.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
#if USE_ATTRIBS
, mesh.nextras
#endif
);
newvertex.type = VertexType.FreeVertex;
// 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.Lprev();
}
// Assign triangle for attributes interpolation.
newvertex.tri.tri = newvertex_tri;
// 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;
#if USE_ATTRIBS
if (mesh.nextras > 0)
{
Interpolation.InterpolateAttributes(newvertex, newvertex.tri.tri, mesh.nextras);
}
#endif
#if USE_Z
Interpolation.InterpolateZ(newvertex, newvertex.tri.tri);
#endif
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 (Log.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>
private 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 (Log.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
}
}