123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 |
- // -----------------------------------------------------------------------
- // <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
- }
- }
|