// ----------------------------------------------------------------------- // // 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/ // // ----------------------------------------------------------------------- namespace UnityEngine.U2D.Animation.TriangleNet { using System; using System.Collections.Generic; using Animation.TriangleNet.Geometry; using Animation.TriangleNet.Logging; using Animation.TriangleNet.Meshing; using Animation.TriangleNet.Meshing.Data; using Animation.TriangleNet.Meshing.Iterators; using Animation.TriangleNet.Tools; using Animation.TriangleNet.Topology; /// /// Mesh data structure. /// internal class Mesh : IMesh { #region Variables IPredicates predicates; ILog logger; QualityMesher qualityMesher; // Stack that maintains a list of recently flipped triangles. Stack flipstack; // TODO: Check if custom hashmap implementation could be faster. // Using hashsets for memory management should quite fast. internal TrianglePool triangles; internal Dictionary subsegs; internal Dictionary vertices; // Hash seeds (should belong to mesh instance) internal int hash_vtx = 0; internal int hash_seg = 0; internal int hash_tri = 0; internal List holes; internal List regions; // TODO: remove mesh_dim, invertices and insegments // Other variables. internal Rectangle bounds; // x and y bounds. internal int invertices; // Number of input vertices. internal int insegments; // Number of input segments. internal int undeads; // Number of input vertices that don't appear in the mesh. internal int mesh_dim; // Dimension (ought to be 2). internal int nextras = 0; // Number of attributes per vertex. //internal int eextras; // Number of attributes per triangle. internal int hullsize; // Number of edges in convex hull. internal int steinerleft; // Number of Steiner points not yet used. internal bool checksegments; // Are there segments in the triangulation yet? internal bool checkquality; // Has quality triangulation begun yet? // Triangular bounding box vertices. internal Vertex infvertex1, infvertex2, infvertex3; internal TriangleLocator locator; // Controls the behavior of the mesh instance. internal Behavior behavior; // The current node numbering internal NodeNumbering numbering; #endregion #region Public properties /// /// Gets the mesh bounding box. /// public Rectangle Bounds { get { return this.bounds; } } /// /// Gets the mesh vertices. /// public ICollection Vertices { get { return this.vertices.Values; } } /// /// Gets the mesh holes. /// public IList Holes { get { return this.holes; } } /// /// Gets the mesh triangles. /// public ICollection Triangles { get { return this.triangles; } } /// /// Gets the mesh segments. /// public ICollection Segments { get { return this.subsegs.Values; } } /// /// Gets the mesh edges. /// public IEnumerable Edges { get { var e = new EdgeIterator(this); while (e.MoveNext()) { yield return e.Current; } } } /// /// Gets the number of input vertices. /// public int NumberOfInputPoints { get { return invertices; } } /// /// Gets the number of mesh edges. /// public int NumberOfEdges { get { return (3 * triangles.Count + hullsize) / 2; } } /// /// Indicates whether the input is a PSLG or a point set. /// public bool IsPolygon { get { return this.insegments > 0; } } /// /// Gets the current node numbering. /// public NodeNumbering CurrentNumbering { get { return numbering; } } #endregion #region "Outer space" variables internal const int DUMMY = -1; // The triangle that fills "outer space," called 'dummytri', is pointed to // by every triangle and subsegment on a boundary (be it outer or inner) of // the triangulation. Also, 'dummytri' points to one of the triangles on // the convex hull (until the holes and concavities are carved), making it // possible to find a starting triangle for point location. // 'dummytri' and 'dummysub' are generally required to fulfill only a few // invariants: their vertices must remain NULL and 'dummytri' must always // be bonded (at offset zero) to some triangle on the convex hull of the // mesh, via a boundary edge. Otherwise, the connections of 'dummytri' and // 'dummysub' may change willy-nilly. This makes it possible to avoid // writing a good deal of special-case code (in the edge flip, for example) // for dealing with the boundary of the mesh, places where no subsegment is // present, and so forth. Other entities are frequently bonded to // 'dummytri' and 'dummysub' as if they were real mesh entities, with no // harm done. internal Triangle dummytri; // Set up 'dummysub', the omnipresent subsegment pointed to by any // triangle side or subsegment end that isn't attached to a real // subsegment. internal SubSegment dummysub; private void Initialize() { dummysub = new SubSegment(); dummysub.hash = DUMMY; // Initialize the two adjoining subsegments to be the omnipresent // subsegment. These will eventually be changed by various bonding // operations, but their values don't really matter, as long as they // can legally be dereferenced. dummysub.subsegs[0].seg = dummysub; dummysub.subsegs[1].seg = dummysub; // Set up 'dummytri', the 'triangle' that occupies "outer space." dummytri = new Triangle(); dummytri.hash = dummytri.id = DUMMY; // Initialize the three adjoining triangles to be "outer space." These // will eventually be changed by various bonding operations, but their // values don't really matter, as long as they can legally be // dereferenced. dummytri.neighbors[0].tri = dummytri; dummytri.neighbors[1].tri = dummytri; dummytri.neighbors[2].tri = dummytri; // Initialize the three adjoining subsegments of 'dummytri' to be // the omnipresent subsegment. dummytri.subsegs[0].seg = dummysub; dummytri.subsegs[1].seg = dummysub; dummytri.subsegs[2].seg = dummysub; } #endregion /// /// Initializes a new instance of the class. /// public Mesh(Configuration config) { Initialize(); logger = Log.Instance; behavior = new Behavior(); vertices = new Dictionary(); subsegs = new Dictionary(); triangles = config.TrianglePool(); flipstack = new Stack(); holes = new List(); regions = new List(); steinerleft = -1; this.predicates = config.Predicates(); this.locator = new TriangleLocator(this, predicates); } public void Refine(QualityOptions quality, bool delaunay = false) { invertices = vertices.Count; if (behavior.Poly) { insegments = behavior.useSegments ? subsegs.Count : hullsize; } Reset(); if (qualityMesher == null) { qualityMesher = new QualityMesher(this, new Configuration()); } // Enforce angle and area constraints. qualityMesher.Apply(quality, delaunay); } /// /// Renumber vertex and triangle id's. /// public void Renumber() { this.Renumber(NodeNumbering.Linear); } /// /// Renumber vertex and triangle id's. /// public void Renumber(NodeNumbering num) { // Don't need to do anything if the nodes are already numbered. if (num == this.numbering) { return; } int id; if (num == NodeNumbering.Linear) { id = 0; foreach (var node in this.vertices.Values) { node.id = id++; } } else if (num == NodeNumbering.CuthillMcKee) { var rcm = new CuthillMcKee(); var iperm = rcm.Renumber(this); // Permute the node indices. foreach (var node in this.vertices.Values) { node.id = iperm[node.id]; } } // Remember the current numbering. numbering = num; // Triangles will always be numbered from 0 to n-1 id = 0; foreach (var item in this.triangles) { item.id = id++; } } #region Misc /// /// Set QualityMesher for mesh refinement. /// /// internal void SetQualityMesher(QualityMesher qmesher) { qualityMesher = qmesher; } internal void CopyTo(Mesh target) { target.vertices = this.vertices; target.triangles = this.triangles; target.subsegs = this.subsegs; target.holes = this.holes; target.regions = this.regions; target.hash_vtx = this.hash_vtx; target.hash_seg = this.hash_seg; target.hash_tri = this.hash_tri; target.numbering = this.numbering; target.hullsize = this.hullsize; } /// /// Reset all the mesh data. This method will also wipe /// out all mesh data. /// private void ResetData() { vertices.Clear(); triangles.Restart(); subsegs.Clear(); holes.Clear(); regions.Clear(); this.hash_vtx = 0; this.hash_seg = 0; this.hash_tri = 0; flipstack.Clear(); hullsize = 0; Reset(); locator.Reset(); } /// /// Reset the mesh triangulation state. /// private void Reset() { numbering = NodeNumbering.None; undeads = 0; // No eliminated input vertices yet. checksegments = false; // There are no segments in the triangulation yet. checkquality = false; // The quality triangulation stage has not begun. Statistic.InCircleCount = 0; Statistic.CounterClockwiseCount = 0; Statistic.InCircleAdaptCount = 0; Statistic.CounterClockwiseAdaptCount = 0; Statistic.Orient3dCount = 0; Statistic.HyperbolaCount = 0; Statistic.CircleTopCount = 0; Statistic.CircumcenterCount = 0; } /// /// Read the vertices from memory. /// /// The input data. internal void TransferNodes(IList points) { this.invertices = points.Count; this.mesh_dim = 2; this.bounds = new Rectangle(); if (this.invertices < 3) { logger.Error("Input must have at least three input vertices.", "Mesh.TransferNodes()"); throw new Exception("Input must have at least three input vertices."); } var v = points[0]; #if USE_ATTRIBS // Check attributes. this.nextras = v.attributes == null ? 0 : v.attributes.Length; #endif // Simple heuristic to check if ids are already set. We assume that if the // first two vertex ids are distinct, then all input vertices have pairwise // distinct ids. bool userId = (v.id != points[1].id); foreach (var p in points) { if (userId) { p.hash = p.id; // Make sure the hash counter gets updated. hash_vtx = Math.Max(p.hash + 1, hash_vtx); } else { p.hash = p.id = hash_vtx++; } this.vertices.Add(p.hash, p); this.bounds.Expand(p); } } /// /// Construct a mapping from vertices to triangles to improve the speed of /// point location for segment insertion. /// /// /// Traverses all the triangles, and provides each corner of each triangle /// with a pointer to that triangle. Of course, pointers will be overwritten /// by other pointers because (almost) each vertex is a corner of several /// triangles, but in the end every vertex will point to some triangle /// that contains it. /// internal void MakeVertexMap() { Otri tri = default(Otri); Vertex triorg; foreach (var t in this.triangles) { tri.tri = t; // Check all three vertices of the triangle. for (tri.orient = 0; tri.orient < 3; tri.orient++) { triorg = tri.Org(); triorg.tri = tri; } } } #endregion #region Factory /// /// Create a new triangle with orientation zero. /// /// Reference to the new triangle. internal void MakeTriangle(ref Otri newotri) { Triangle tri = triangles.Get(); //tri.id = tri.hash; tri.subsegs[0].seg = dummysub; tri.subsegs[1].seg = dummysub; tri.subsegs[2].seg = dummysub; tri.neighbors[0].tri = dummytri; tri.neighbors[1].tri = dummytri; tri.neighbors[2].tri = dummytri; newotri.tri = tri; newotri.orient = 0; } /// /// Create a new subsegment with orientation zero. /// /// Reference to the new subseg. internal void MakeSegment(ref Osub newsubseg) { var seg = new SubSegment(); seg.hash = this.hash_seg++; seg.subsegs[0].seg = dummysub; seg.subsegs[1].seg = dummysub; seg.triangles[0].tri = dummytri; seg.triangles[1].tri = dummytri; newsubseg.seg = seg; newsubseg.orient = 0; subsegs.Add(seg.hash, seg); } #endregion #region Manipulation /// /// Insert a vertex into a Delaunay triangulation, performing flips as necessary /// to maintain the Delaunay property. /// /// The point to be inserted. /// The triangle to start the search. /// Segment to split. /// Check for creation of encroached subsegments. /// Check for creation of bad quality triangles. /// If a duplicate vertex or violated segment does not prevent the /// vertex from being inserted, the return value will be ENCROACHINGVERTEX if /// the vertex encroaches upon a subsegment (and checking is enabled), or /// SUCCESSFULVERTEX otherwise. In either case, 'searchtri' is set to a handle /// whose origin is the newly inserted vertex. /// /// The point 'newvertex' is located. If 'searchtri.triangle' is not NULL, /// the search for the containing triangle begins from 'searchtri'. If /// 'searchtri.triangle' is NULL, a full point location procedure is called. /// If 'insertvertex' is found inside a triangle, the triangle is split into /// three; if 'insertvertex' lies on an edge, the edge is split in two, /// thereby splitting the two adjacent triangles into four. Edge flips are /// used to restore the Delaunay property. If 'insertvertex' lies on an /// existing vertex, no action is taken, and the value DUPLICATEVERTEX is /// returned. On return, 'searchtri' is set to a handle whose origin is the /// existing vertex. /// /// InsertVertex() does not use flip() for reasons of speed; some /// information can be reused from edge flip to edge flip, like the /// locations of subsegments. /// /// Param 'splitseg': Normally, the parameter 'splitseg' is set to NULL, /// implying that no subsegment should be split. In this case, if 'insertvertex' /// is found to lie on a segment, no action is taken, and the value VIOLATINGVERTEX /// is returned. On return, 'searchtri' is set to a handle whose primary edge is the /// violated subsegment. /// If the calling routine wishes to split a subsegment by inserting a vertex in it, /// the parameter 'splitseg' should be that subsegment. In this case, 'searchtri' /// MUST be the triangle handle reached by pivoting from that subsegment; no point /// location is done. /// /// Param 'segmentflaws': Flags that indicate whether or not there should /// be checks for the creation of encroached subsegments. If a newly inserted /// vertex encroaches upon subsegments, these subsegments are added to the list /// of subsegments to be split if 'segmentflaws' is set. /// /// Param 'triflaws': Flags that indicate whether or not there should be /// checks for the creation of bad quality triangles. If bad triangles are /// created, these are added to the queue if 'triflaws' is set. /// internal InsertVertexResult InsertVertex(Vertex newvertex, ref Otri searchtri, ref Osub splitseg, bool segmentflaws, bool triflaws) { Otri horiz = default(Otri); Otri top = default(Otri); Otri botleft = default(Otri), botright = default(Otri); Otri topleft = default(Otri), topright = default(Otri); Otri newbotleft = default(Otri), newbotright = default(Otri); Otri newtopright = default(Otri); Otri botlcasing = default(Otri), botrcasing = default(Otri); Otri toplcasing = default(Otri), toprcasing = default(Otri); Otri testtri = default(Otri); Osub botlsubseg = default(Osub), botrsubseg = default(Osub); Osub toplsubseg = default(Osub), toprsubseg = default(Osub); Osub brokensubseg = default(Osub); Osub checksubseg = default(Osub); Osub rightsubseg = default(Osub); Osub newsubseg = default(Osub); BadSubseg encroached; //FlipStacker newflip; Vertex first; Vertex leftvertex, rightvertex, botvertex, topvertex, farvertex; Vertex segmentorg, segmentdest; int region; double area; InsertVertexResult success; LocateResult intersect; bool doflip; bool mirrorflag; bool enq; if (splitseg.seg == null) { // Find the location of the vertex to be inserted. Check if a good // starting triangle has already been provided by the caller. if (searchtri.tri.id == DUMMY) { // Find a boundary triangle. horiz.tri = dummytri; horiz.orient = 0; horiz.Sym(); // Search for a triangle containing 'newvertex'. intersect = locator.Locate(newvertex, ref horiz); } else { // Start searching from the triangle provided by the caller. searchtri.Copy(ref horiz); intersect = locator.PreciseLocate(newvertex, ref horiz, true); } } else { // The calling routine provides the subsegment in which // the vertex is inserted. searchtri.Copy(ref horiz); intersect = LocateResult.OnEdge; } if (intersect == LocateResult.OnVertex) { // There's already a vertex there. Return in 'searchtri' a triangle // whose origin is the existing vertex. horiz.Copy(ref searchtri); locator.Update(ref horiz); return InsertVertexResult.Duplicate; } if ((intersect == LocateResult.OnEdge) || (intersect == LocateResult.Outside)) { // The vertex falls on an edge or boundary. if (checksegments && (splitseg.seg == null)) { // Check whether the vertex falls on a subsegment. horiz.Pivot(ref brokensubseg); if (brokensubseg.seg.hash != DUMMY) { // The vertex falls on a subsegment, and hence will not be inserted. if (segmentflaws) { enq = behavior.NoBisect != 2; if (enq && (behavior.NoBisect == 1)) { // This subsegment may be split only if it is an // internal boundary. horiz.Sym(ref testtri); enq = testtri.tri.id != DUMMY; } if (enq) { // Add the subsegment to the list of encroached subsegments. encroached = new BadSubseg(); encroached.subseg = brokensubseg; encroached.org = brokensubseg.Org(); encroached.dest = brokensubseg.Dest(); qualityMesher.AddBadSubseg(encroached); } } // Return a handle whose primary edge contains the vertex, // which has not been inserted. horiz.Copy(ref searchtri); locator.Update(ref horiz); return InsertVertexResult.Violating; } } // Insert the vertex on an edge, dividing one triangle into two (if // the edge lies on a boundary) or two triangles into four. horiz.Lprev(ref botright); botright.Sym(ref botrcasing); horiz.Sym(ref topright); // Is there a second triangle? (Or does this edge lie on a boundary?) mirrorflag = topright.tri.id != DUMMY; if (mirrorflag) { topright.Lnext(); topright.Sym(ref toprcasing); MakeTriangle(ref newtopright); } else { // Splitting a boundary edge increases the number of boundary edges. hullsize++; } MakeTriangle(ref newbotright); // Set the vertices of changed and new triangles. rightvertex = horiz.Org(); leftvertex = horiz.Dest(); botvertex = horiz.Apex(); newbotright.SetOrg(botvertex); newbotright.SetDest(rightvertex); newbotright.SetApex(newvertex); horiz.SetOrg(newvertex); // Set the region of a new triangle. newbotright.tri.label = botright.tri.label; if (behavior.VarArea) { // Set the area constraint of a new triangle. newbotright.tri.area = botright.tri.area; } if (mirrorflag) { topvertex = topright.Dest(); newtopright.SetOrg(rightvertex); newtopright.SetDest(topvertex); newtopright.SetApex(newvertex); topright.SetOrg(newvertex); // Set the region of another new triangle. newtopright.tri.label = topright.tri.label; if (behavior.VarArea) { // Set the area constraint of another new triangle. newtopright.tri.area = topright.tri.area; } } // There may be subsegments that need to be bonded // to the new triangle(s). if (checksegments) { botright.Pivot(ref botrsubseg); if (botrsubseg.seg.hash != DUMMY) { botright.SegDissolve(dummysub); newbotright.SegBond(ref botrsubseg); } if (mirrorflag) { topright.Pivot(ref toprsubseg); if (toprsubseg.seg.hash != DUMMY) { topright.SegDissolve(dummysub); newtopright.SegBond(ref toprsubseg); } } } // Bond the new triangle(s) to the surrounding triangles. newbotright.Bond(ref botrcasing); newbotright.Lprev(); newbotright.Bond(ref botright); newbotright.Lprev(); if (mirrorflag) { newtopright.Bond(ref toprcasing); newtopright.Lnext(); newtopright.Bond(ref topright); newtopright.Lnext(); newtopright.Bond(ref newbotright); } if (splitseg.seg != null) { // Split the subsegment into two. splitseg.SetDest(newvertex); segmentorg = splitseg.SegOrg(); segmentdest = splitseg.SegDest(); splitseg.Sym(); splitseg.Pivot(ref rightsubseg); InsertSubseg(ref newbotright, splitseg.seg.boundary); newbotright.Pivot(ref newsubseg); newsubseg.SetSegOrg(segmentorg); newsubseg.SetSegDest(segmentdest); splitseg.Bond(ref newsubseg); newsubseg.Sym(); newsubseg.Bond(ref rightsubseg); splitseg.Sym(); // Transfer the subsegment's boundary marker to the vertex if required. if (newvertex.label == 0) { newvertex.label = splitseg.seg.boundary; } } if (checkquality) { flipstack.Clear(); flipstack.Push(default(Otri)); // Dummy flip (see UndoVertex) flipstack.Push(horiz); } // Position 'horiz' on the first edge to check for // the Delaunay property. horiz.Lnext(); } else { // Insert the vertex in a triangle, splitting it into three. horiz.Lnext(ref botleft); horiz.Lprev(ref botright); botleft.Sym(ref botlcasing); botright.Sym(ref botrcasing); MakeTriangle(ref newbotleft); MakeTriangle(ref newbotright); // Set the vertices of changed and new triangles. rightvertex = horiz.Org(); leftvertex = horiz.Dest(); botvertex = horiz.Apex(); newbotleft.SetOrg(leftvertex); newbotleft.SetDest(botvertex); newbotleft.SetApex(newvertex); newbotright.SetOrg(botvertex); newbotright.SetDest(rightvertex); newbotright.SetApex(newvertex); horiz.SetApex(newvertex); // Set the region of the new triangles. newbotleft.tri.label = horiz.tri.label; newbotright.tri.label = horiz.tri.label; if (behavior.VarArea) { // Set the area constraint of the new triangles. area = horiz.tri.area; newbotleft.tri.area = area; newbotright.tri.area = area; } // There may be subsegments that need to be bonded // to the new triangles. if (checksegments) { botleft.Pivot(ref botlsubseg); if (botlsubseg.seg.hash != DUMMY) { botleft.SegDissolve(dummysub); newbotleft.SegBond(ref botlsubseg); } botright.Pivot(ref botrsubseg); if (botrsubseg.seg.hash != DUMMY) { botright.SegDissolve(dummysub); newbotright.SegBond(ref botrsubseg); } } // Bond the new triangles to the surrounding triangles. newbotleft.Bond(ref botlcasing); newbotright.Bond(ref botrcasing); newbotleft.Lnext(); newbotright.Lprev(); newbotleft.Bond(ref newbotright); newbotleft.Lnext(); botleft.Bond(ref newbotleft); newbotright.Lprev(); botright.Bond(ref newbotright); if (checkquality) { flipstack.Clear(); flipstack.Push(horiz); } } // The insertion is successful by default, unless an encroached // subsegment is found. success = InsertVertexResult.Successful; if (newvertex.tri.tri != null) { // Store the coordinates of the triangle that contains newvertex. newvertex.tri.SetOrg(rightvertex); newvertex.tri.SetDest(leftvertex); newvertex.tri.SetApex(botvertex); } // Circle around the newly inserted vertex, checking each edge opposite it // for the Delaunay property. Non-Delaunay edges are flipped. 'horiz' is // always the edge being checked. 'first' marks where to stop circling. first = horiz.Org(); rightvertex = first; leftvertex = horiz.Dest(); // Circle until finished. while (true) { // By default, the edge will be flipped. doflip = true; if (checksegments) { // Check for a subsegment, which cannot be flipped. horiz.Pivot(ref checksubseg); if (checksubseg.seg.hash != DUMMY) { // The edge is a subsegment and cannot be flipped. doflip = false; if (segmentflaws) { // Does the new vertex encroach upon this subsegment? if (qualityMesher.CheckSeg4Encroach(ref checksubseg) > 0) { success = InsertVertexResult.Encroaching; } } } } if (doflip) { // Check if the edge is a boundary edge. horiz.Sym(ref top); if (top.tri.id == DUMMY) { // The edge is a boundary edge and cannot be flipped. doflip = false; } else { // Find the vertex on the other side of the edge. farvertex = top.Apex(); // In the incremental Delaunay triangulation algorithm, any of // 'leftvertex', 'rightvertex', and 'farvertex' could be vertices // of the triangular bounding box. These vertices must be // treated as if they are infinitely distant, even though their // "coordinates" are not. if ((leftvertex == infvertex1) || (leftvertex == infvertex2) || (leftvertex == infvertex3)) { // 'leftvertex' is infinitely distant. Check the convexity of // the boundary of the triangulation. 'farvertex' might be // infinite as well, but trust me, this same condition should // be applied. doflip = predicates.CounterClockwise(newvertex, rightvertex, farvertex) > 0.0; } else if ((rightvertex == infvertex1) || (rightvertex == infvertex2) || (rightvertex == infvertex3)) { // 'rightvertex' is infinitely distant. Check the convexity of // the boundary of the triangulation. 'farvertex' might be // infinite as well, but trust me, this same condition should // be applied. doflip = predicates.CounterClockwise(farvertex, leftvertex, newvertex) > 0.0; } else if ((farvertex == infvertex1) || (farvertex == infvertex2) || (farvertex == infvertex3)) { // 'farvertex' is infinitely distant and cannot be inside // the circumcircle of the triangle 'horiz'. doflip = false; } else { // Test whether the edge is locally Delaunay. doflip = predicates.InCircle(leftvertex, newvertex, rightvertex, farvertex) > 0.0; } if (doflip) { // We made it! Flip the edge 'horiz' by rotating its containing // quadrilateral (the two triangles adjacent to 'horiz'). // Identify the casing of the quadrilateral. top.Lprev(ref topleft); topleft.Sym(ref toplcasing); top.Lnext(ref topright); topright.Sym(ref toprcasing); horiz.Lnext(ref botleft); botleft.Sym(ref botlcasing); horiz.Lprev(ref botright); botright.Sym(ref botrcasing); // Rotate the quadrilateral one-quarter turn counterclockwise. topleft.Bond(ref botlcasing); botleft.Bond(ref botrcasing); botright.Bond(ref toprcasing); topright.Bond(ref toplcasing); if (checksegments) { // Check for subsegments and rebond them to the quadrilateral. topleft.Pivot(ref toplsubseg); botleft.Pivot(ref botlsubseg); botright.Pivot(ref botrsubseg); topright.Pivot(ref toprsubseg); if (toplsubseg.seg.hash == DUMMY) { topright.SegDissolve(dummysub); } else { topright.SegBond(ref toplsubseg); } if (botlsubseg.seg.hash == DUMMY) { topleft.SegDissolve(dummysub); } else { topleft.SegBond(ref botlsubseg); } if (botrsubseg.seg.hash == DUMMY) { botleft.SegDissolve(dummysub); } else { botleft.SegBond(ref botrsubseg); } if (toprsubseg.seg.hash == DUMMY) { botright.SegDissolve(dummysub); } else { botright.SegBond(ref toprsubseg); } } // New vertex assignments for the rotated quadrilateral. horiz.SetOrg(farvertex); horiz.SetDest(newvertex); horiz.SetApex(rightvertex); top.SetOrg(newvertex); top.SetDest(farvertex); top.SetApex(leftvertex); // Assign region. // TODO: check region ok (no Math.Min necessary) region = Math.Min(top.tri.label, horiz.tri.label); top.tri.label = region; horiz.tri.label = region; if (behavior.VarArea) { if ((top.tri.area <= 0.0) || (horiz.tri.area <= 0.0)) { area = -1.0; } else { // Take the average of the two triangles' area constraints. // This prevents small area constraints from migrating a // long, long way from their original location due to flips. area = 0.5 * (top.tri.area + horiz.tri.area); } top.tri.area = area; horiz.tri.area = area; } if (checkquality) { flipstack.Push(horiz); } // On the next iterations, consider the two edges that were exposed (this // is, are now visible to the newly inserted vertex) by the edge flip. horiz.Lprev(); leftvertex = farvertex; } } } if (!doflip) { // The handle 'horiz' is accepted as locally Delaunay. if (triflaws) { // Check the triangle 'horiz' for quality. qualityMesher.TestTriangle(ref horiz); } // Look for the next edge around the newly inserted vertex. horiz.Lnext(); horiz.Sym(ref testtri); // Check for finishing a complete revolution about the new vertex, or // falling outside of the triangulation. The latter will happen when // a vertex is inserted at a boundary. if ((leftvertex == first) || (testtri.tri.id == DUMMY)) { // We're done. Return a triangle whose origin is the new vertex. horiz.Lnext(ref searchtri); Otri recenttri = default(Otri); horiz.Lnext(ref recenttri); locator.Update(ref recenttri); return success; } // Finish finding the next edge around the newly inserted vertex. testtri.Lnext(ref horiz); rightvertex = leftvertex; leftvertex = horiz.Dest(); } } } /// /// Create a new subsegment and inserts it between two triangles. Its /// vertices are properly initialized. /// /// The new subsegment is inserted at the edge /// described by this handle. /// The marker 'subsegmark' is applied to the /// subsegment and, if appropriate, its vertices. internal void InsertSubseg(ref Otri tri, int subsegmark) { Otri oppotri = default(Otri); Osub newsubseg = default(Osub); Vertex triorg, tridest; triorg = tri.Org(); tridest = tri.Dest(); // Mark vertices if possible. if (triorg.label == 0) { triorg.label = subsegmark; } if (tridest.label == 0) { tridest.label = subsegmark; } // Check if there's already a subsegment here. tri.Pivot(ref newsubseg); if (newsubseg.seg.hash == DUMMY) { // Make new subsegment and initialize its vertices. MakeSegment(ref newsubseg); newsubseg.SetOrg(tridest); newsubseg.SetDest(triorg); newsubseg.SetSegOrg(tridest); newsubseg.SetSegDest(triorg); // Bond new subsegment to the two triangles it is sandwiched between. // Note that the facing triangle 'oppotri' might be equal to 'dummytri' // (outer space), but the new subsegment is bonded to it all the same. tri.SegBond(ref newsubseg); tri.Sym(ref oppotri); newsubseg.Sym(); oppotri.SegBond(ref newsubseg); newsubseg.seg.boundary = subsegmark; } else if (newsubseg.seg.boundary == 0) { newsubseg.seg.boundary = subsegmark; } } /// /// Transform two triangles to two different triangles by flipping an edge /// counterclockwise within a quadrilateral. /// /// Handle to the edge that will be flipped. /// Imagine the original triangles, abc and bad, oriented so that the /// shared edge ab lies in a horizontal plane, with the vertex b on the left /// and the vertex a on the right. The vertex c lies below the edge, and /// the vertex d lies above the edge. The 'flipedge' handle holds the edge /// ab of triangle abc, and is directed left, from vertex a to vertex b. /// /// The triangles abc and bad are deleted and replaced by the triangles cdb /// and dca. The triangles that represent abc and bad are NOT deallocated; /// they are reused for dca and cdb, respectively. Hence, any handles that /// may have held the original triangles are still valid, although not /// directed as they were before. /// /// Upon completion of this routine, the 'flipedge' handle holds the edge /// dc of triangle dca, and is directed down, from vertex d to vertex c. /// (Hence, the two triangles have rotated counterclockwise.) /// /// WARNING: This transformation is geometrically valid only if the /// quadrilateral adbc is convex. Furthermore, this transformation is /// valid only if there is not a subsegment between the triangles abc and /// bad. This routine does not check either of these preconditions, and /// it is the responsibility of the calling routine to ensure that they are /// met. If they are not, the streets shall be filled with wailing and /// gnashing of teeth. /// /// Terminology /// /// A "local transformation" replaces a small set of triangles with another /// set of triangles. This may or may not involve inserting or deleting a /// vertex. /// /// The term "casing" is used to describe the set of triangles that are /// attached to the triangles being transformed, but are not transformed /// themselves. Think of the casing as a fixed hollow structure inside /// which all the action happens. A "casing" is only defined relative to /// a single transformation; each occurrence of a transformation will /// involve a different casing. /// internal void Flip(ref Otri flipedge) { Otri botleft = default(Otri), botright = default(Otri); Otri topleft = default(Otri), topright = default(Otri); Otri top = default(Otri); Otri botlcasing = default(Otri), botrcasing = default(Otri); Otri toplcasing = default(Otri), toprcasing = default(Otri); Osub botlsubseg = default(Osub), botrsubseg = default(Osub); Osub toplsubseg = default(Osub), toprsubseg = default(Osub); Vertex leftvertex, rightvertex, botvertex; Vertex farvertex; // Identify the vertices of the quadrilateral. rightvertex = flipedge.Org(); leftvertex = flipedge.Dest(); botvertex = flipedge.Apex(); flipedge.Sym(ref top); // SELF CHECK //if (top.triangle.id == DUMMY) //{ // logger.Error("Attempt to flip on boundary.", "Mesh.Flip()"); // flipedge.LnextSelf(); // return; //} //if (checksegments) //{ // flipedge.SegPivot(ref toplsubseg); // if (toplsubseg.ss != Segment.Empty) // { // logger.Error("Attempt to flip a segment.", "Mesh.Flip()"); // flipedge.LnextSelf(); // return; // } //} farvertex = top.Apex(); // Identify the casing of the quadrilateral. top.Lprev(ref topleft); topleft.Sym(ref toplcasing); top.Lnext(ref topright); topright.Sym(ref toprcasing); flipedge.Lnext(ref botleft); botleft.Sym(ref botlcasing); flipedge.Lprev(ref botright); botright.Sym(ref botrcasing); // Rotate the quadrilateral one-quarter turn counterclockwise. topleft.Bond(ref botlcasing); botleft.Bond(ref botrcasing); botright.Bond(ref toprcasing); topright.Bond(ref toplcasing); if (checksegments) { // Check for subsegments and rebond them to the quadrilateral. topleft.Pivot(ref toplsubseg); botleft.Pivot(ref botlsubseg); botright.Pivot(ref botrsubseg); topright.Pivot(ref toprsubseg); if (toplsubseg.seg.hash == DUMMY) { topright.SegDissolve(dummysub); } else { topright.SegBond(ref toplsubseg); } if (botlsubseg.seg.hash == DUMMY) { topleft.SegDissolve(dummysub); } else { topleft.SegBond(ref botlsubseg); } if (botrsubseg.seg.hash == DUMMY) { botleft.SegDissolve(dummysub); } else { botleft.SegBond(ref botrsubseg); } if (toprsubseg.seg.hash == DUMMY) { botright.SegDissolve(dummysub); } else { botright.SegBond(ref toprsubseg); } } // New vertex assignments for the rotated quadrilateral. flipedge.SetOrg(farvertex); flipedge.SetDest(botvertex); flipedge.SetApex(rightvertex); top.SetOrg(botvertex); top.SetDest(farvertex); top.SetApex(leftvertex); } /// /// Transform two triangles to two different triangles by flipping an edge /// clockwise within a quadrilateral. Reverses the flip() operation so that /// the data structures representing the triangles are back where they were /// before the flip(). /// /// /// /// See above Flip() remarks for more information. /// /// Upon completion of this routine, the 'flipedge' handle holds the edge /// cd of triangle cdb, and is directed up, from vertex c to vertex d. /// (Hence, the two triangles have rotated clockwise.) /// internal void Unflip(ref Otri flipedge) { Otri botleft = default(Otri), botright = default(Otri); Otri topleft = default(Otri), topright = default(Otri); Otri top = default(Otri); Otri botlcasing = default(Otri), botrcasing = default(Otri); Otri toplcasing = default(Otri), toprcasing = default(Otri); Osub botlsubseg = default(Osub), botrsubseg = default(Osub); Osub toplsubseg = default(Osub), toprsubseg = default(Osub); Vertex leftvertex, rightvertex, botvertex; Vertex farvertex; // Identify the vertices of the quadrilateral. rightvertex = flipedge.Org(); leftvertex = flipedge.Dest(); botvertex = flipedge.Apex(); flipedge.Sym(ref top); farvertex = top.Apex(); // Identify the casing of the quadrilateral. top.Lprev(ref topleft); topleft.Sym(ref toplcasing); top.Lnext(ref topright); topright.Sym(ref toprcasing); flipedge.Lnext(ref botleft); botleft.Sym(ref botlcasing); flipedge.Lprev(ref botright); botright.Sym(ref botrcasing); // Rotate the quadrilateral one-quarter turn clockwise. topleft.Bond(ref toprcasing); botleft.Bond(ref toplcasing); botright.Bond(ref botlcasing); topright.Bond(ref botrcasing); if (checksegments) { // Check for subsegments and rebond them to the quadrilateral. topleft.Pivot(ref toplsubseg); botleft.Pivot(ref botlsubseg); botright.Pivot(ref botrsubseg); topright.Pivot(ref toprsubseg); if (toplsubseg.seg.hash == DUMMY) { botleft.SegDissolve(dummysub); } else { botleft.SegBond(ref toplsubseg); } if (botlsubseg.seg.hash == DUMMY) { botright.SegDissolve(dummysub); } else { botright.SegBond(ref botlsubseg); } if (botrsubseg.seg.hash == DUMMY) { topright.SegDissolve(dummysub); } else { topright.SegBond(ref botrsubseg); } if (toprsubseg.seg.hash == DUMMY) { topleft.SegDissolve(dummysub); } else { topleft.SegBond(ref toprsubseg); } } // New vertex assignments for the rotated quadrilateral. flipedge.SetOrg(botvertex); flipedge.SetDest(farvertex); flipedge.SetApex(leftvertex); top.SetOrg(farvertex); top.SetDest(botvertex); top.SetApex(rightvertex); } /// /// Find the Delaunay triangulation of a polygon that has a certain "nice" shape. /// This includes the polygons that result from deletion of a vertex or insertion /// of a segment. /// /// The primary edge of the first triangle. /// The primary edge of the last triangle. /// The number of sides of the polygon, including its /// base. /// A flag, wether to perform the last flip. /// A flag that determines whether the new triangles should /// be tested for quality, and enqueued if they are bad. /// // This is a conceptually difficult routine. The starting assumption is // that we have a polygon with n sides. n - 1 of these sides are currently // represented as edges in the mesh. One side, called the "base", need not // be. // // Inside the polygon is a structure I call a "fan", consisting of n - 1 // triangles that share a common origin. For each of these triangles, the // edge opposite the origin is one of the sides of the polygon. The // primary edge of each triangle is the edge directed from the origin to // the destination; note that this is not the same edge that is a side of // the polygon. 'firstedge' is the primary edge of the first triangle. // From there, the triangles follow in counterclockwise order about the // polygon, until 'lastedge', the primary edge of the last triangle. // 'firstedge' and 'lastedge' are probably connected to other triangles // beyond the extremes of the fan, but their identity is not important, as // long as the fan remains connected to them. // // Imagine the polygon oriented so that its base is at the bottom. This // puts 'firstedge' on the far right, and 'lastedge' on the far left. // The right vertex of the base is the destination of 'firstedge', and the // left vertex of the base is the apex of 'lastedge'. // // The challenge now is to find the right sequence of edge flips to // transform the fan into a Delaunay triangulation of the polygon. Each // edge flip effectively removes one triangle from the fan, committing it // to the polygon. The resulting polygon has one fewer edge. If 'doflip' // is set, the final flip will be performed, resulting in a fan of one // (useless?) triangle. If 'doflip' is not set, the final flip is not // performed, resulting in a fan of two triangles, and an unfinished // triangular polygon that is not yet filled out with a single triangle. // On completion of the routine, 'lastedge' is the last remaining triangle, // or the leftmost of the last two. // // Although the flips are performed in the order described above, the // decisions about what flips to perform are made in precisely the reverse // order. The recursive triangulatepolygon() procedure makes a decision, // uses up to two recursive calls to triangulate the "subproblems" // (polygons with fewer edges), and then performs an edge flip. // // The "decision" it makes is which vertex of the polygon should be // connected to the base. This decision is made by testing every possible // vertex. Once the best vertex is found, the two edges that connect this // vertex to the base become the bases for two smaller polygons. These // are triangulated recursively. Unfortunately, this approach can take // O(n^2) time not only in the worst case, but in many common cases. It's // rarely a big deal for vertex deletion, where n is rarely larger than // ten, but it could be a big deal for segment insertion, especially if // there's a lot of long segments that each cut many triangles. I ought to // code a faster algorithm some day. /// private void TriangulatePolygon(Otri firstedge, Otri lastedge, int edgecount, bool doflip, bool triflaws) { Otri testtri = default(Otri); Otri besttri = default(Otri); Otri tempedge = default(Otri); Vertex leftbasevertex, rightbasevertex; Vertex testvertex; Vertex bestvertex; int bestnumber = 1; // Identify the base vertices. leftbasevertex = lastedge.Apex(); rightbasevertex = firstedge.Dest(); // Find the best vertex to connect the base to. firstedge.Onext(ref besttri); bestvertex = besttri.Dest(); besttri.Copy(ref testtri); for (int i = 2; i <= edgecount - 2; i++) { testtri.Onext(); testvertex = testtri.Dest(); // Is this a better vertex? if (predicates.InCircle(leftbasevertex, rightbasevertex, bestvertex, testvertex) > 0.0) { testtri.Copy(ref besttri); bestvertex = testvertex; bestnumber = i; } } if (bestnumber > 1) { // Recursively triangulate the smaller polygon on the right. besttri.Oprev(ref tempedge); TriangulatePolygon(firstedge, tempedge, bestnumber + 1, true, triflaws); } if (bestnumber < edgecount - 2) { // Recursively triangulate the smaller polygon on the left. besttri.Sym(ref tempedge); TriangulatePolygon(besttri, lastedge, edgecount - bestnumber, true, triflaws); // Find 'besttri' again; it may have been lost to edge flips. tempedge.Sym(ref besttri); } if (doflip) { // Do one final edge flip. Flip(ref besttri); if (triflaws) { // Check the quality of the newly committed triangle. besttri.Sym(ref testtri); qualityMesher.TestTriangle(ref testtri); } } // Return the base triangle. besttri.Copy(ref lastedge); } /// /// Delete a vertex from a Delaunay triangulation, ensuring that the /// triangulation remains Delaunay. /// /// /// The origin of 'deltri' is deleted. The union of the triangles /// adjacent to this vertex is a polygon, for which the Delaunay triangulation /// is found. Two triangles are removed from the mesh. /// /// Only interior vertices that do not lie on segments or boundaries /// may be deleted. /// internal void DeleteVertex(ref Otri deltri) { Otri countingtri = default(Otri); Otri firstedge = default(Otri), lastedge = default(Otri); Otri deltriright = default(Otri); Otri lefttri = default(Otri), righttri = default(Otri); Otri leftcasing = default(Otri), rightcasing = default(Otri); Osub leftsubseg = default(Osub), rightsubseg = default(Osub); Vertex delvertex; Vertex neworg; int edgecount; delvertex = deltri.Org(); VertexDealloc(delvertex); // Count the degree of the vertex being deleted. deltri.Onext(ref countingtri); edgecount = 1; while (!deltri.Equals(countingtri)) { edgecount++; countingtri.Onext(); } if (edgecount > 3) { // Triangulate the polygon defined by the union of all triangles // adjacent to the vertex being deleted. Check the quality of // the resulting triangles. deltri.Onext(ref firstedge); deltri.Oprev(ref lastedge); TriangulatePolygon(firstedge, lastedge, edgecount, false, behavior.NoBisect == 0); } // Splice out two triangles. deltri.Lprev(ref deltriright); deltri.Dnext(ref lefttri); lefttri.Sym(ref leftcasing); deltriright.Oprev(ref righttri); righttri.Sym(ref rightcasing); deltri.Bond(ref leftcasing); deltriright.Bond(ref rightcasing); lefttri.Pivot(ref leftsubseg); if (leftsubseg.seg.hash != DUMMY) { deltri.SegBond(ref leftsubseg); } righttri.Pivot(ref rightsubseg); if (rightsubseg.seg.hash != DUMMY) { deltriright.SegBond(ref rightsubseg); } // Set the new origin of 'deltri' and check its quality. neworg = lefttri.Org(); deltri.SetOrg(neworg); if (behavior.NoBisect == 0) { qualityMesher.TestTriangle(ref deltri); } // Delete the two spliced-out triangles. TriangleDealloc(lefttri.tri); TriangleDealloc(righttri.tri); } /// /// Undo the most recent vertex insertion. /// /// /// Walks through the list of transformations (flips and a vertex insertion) /// in the reverse of the order in which they were done, and undoes them. /// The inserted vertex is removed from the triangulation and deallocated. /// Two triangles (possibly just one) are also deallocated. /// internal void UndoVertex() { Otri fliptri; Otri botleft = default(Otri), botright = default(Otri), topright = default(Otri); Otri botlcasing = default(Otri), botrcasing = default(Otri), toprcasing = default(Otri); Otri gluetri = default(Otri); Osub botlsubseg = default(Osub), botrsubseg = default(Osub), toprsubseg = default(Osub); Vertex botvertex, rightvertex; // Walk through the list of transformations (flips and a vertex insertion) // in the reverse of the order in which they were done, and undo them. while (flipstack.Count > 0) { // Find a triangle involved in the last unreversed transformation. fliptri = flipstack.Pop(); // We are reversing one of three transformations: a trisection of one // triangle into three (by inserting a vertex in the triangle), a // bisection of two triangles into four (by inserting a vertex in an // edge), or an edge flip. if (flipstack.Count == 0) { // Restore a triangle that was split into three triangles, // so it is again one triangle. fliptri.Dprev(ref botleft); botleft.Lnext(); fliptri.Onext(ref botright); botright.Lprev(); botleft.Sym(ref botlcasing); botright.Sym(ref botrcasing); botvertex = botleft.Dest(); fliptri.SetApex(botvertex); fliptri.Lnext(); fliptri.Bond(ref botlcasing); botleft.Pivot(ref botlsubseg); fliptri.SegBond(ref botlsubseg); fliptri.Lnext(); fliptri.Bond(ref botrcasing); botright.Pivot(ref botrsubseg); fliptri.SegBond(ref botrsubseg); // Delete the two spliced-out triangles. TriangleDealloc(botleft.tri); TriangleDealloc(botright.tri); } else if (flipstack.Peek().tri == null) // Dummy flip { // Restore two triangles that were split into four triangles, // so they are again two triangles. fliptri.Lprev(ref gluetri); gluetri.Sym(ref botright); botright.Lnext(); botright.Sym(ref botrcasing); rightvertex = botright.Dest(); fliptri.SetOrg(rightvertex); gluetri.Bond(ref botrcasing); botright.Pivot(ref botrsubseg); gluetri.SegBond(ref botrsubseg); // Delete the spliced-out triangle. TriangleDealloc(botright.tri); fliptri.Sym(ref gluetri); if (gluetri.tri.id != DUMMY) { gluetri.Lnext(); gluetri.Dnext(ref topright); topright.Sym(ref toprcasing); gluetri.SetOrg(rightvertex); gluetri.Bond(ref toprcasing); topright.Pivot(ref toprsubseg); gluetri.SegBond(ref toprsubseg); // Delete the spliced-out triangle. TriangleDealloc(topright.tri); } flipstack.Clear(); } else { // Undo an edge flip. Unflip(ref fliptri); } } } #endregion #region Dealloc /// /// Deallocate space for a triangle, marking it dead. /// /// internal void TriangleDealloc(Triangle dyingtriangle) { // Mark the triangle as dead. This makes it possible to detect dead // triangles when traversing the list of all triangles. Otri.Kill(dyingtriangle); triangles.Release(dyingtriangle); } /// /// Deallocate space for a vertex, marking it dead. /// /// internal void VertexDealloc(Vertex dyingvertex) { // Mark the vertex as dead. This makes it possible to detect dead // vertices when traversing the list of all vertices. dyingvertex.type = VertexType.DeadVertex; vertices.Remove(dyingvertex.hash); } /// /// Deallocate space for a subsegment, marking it dead. /// /// internal void SubsegDealloc(SubSegment dyingsubseg) { // Mark the subsegment as dead. This makes it possible to detect dead // subsegments when traversing the list of all subsegments. Osub.Kill(dyingsubseg); subsegs.Remove(dyingsubseg.hash); } #endregion } }