using System; using System.Collections.Generic; using TriangleNet.Geometry; using TriangleNet.Logging; using TriangleNet.Meshing; using TriangleNet.Meshing.Data; using TriangleNet.Meshing.Iterators; using TriangleNet.Tools; using TriangleNet.Topology; namespace TriangleNet { public class Mesh : IMesh { private IPredicates predicates; private ILog logger; private QualityMesher qualityMesher; private Stack flipstack; internal TrianglePool triangles; internal Dictionary subsegs; internal Dictionary vertices; internal int hash_vtx; internal int hash_seg; internal int hash_tri; internal List holes; internal List regions; internal Rectangle bounds; internal int invertices; internal int insegments; internal int undeads; internal int mesh_dim; internal int nextras; internal int hullsize; internal int steinerleft; internal bool checksegments; internal bool checkquality; internal Vertex infvertex1; internal Vertex infvertex2; internal Vertex infvertex3; internal TriangleLocator locator; internal Behavior behavior; internal NodeNumbering numbering; internal const int DUMMY = -1; internal Triangle dummytri; internal SubSegment dummysub; public Rectangle Bounds => bounds; public ICollection Vertices => vertices.Values; public IList Holes => holes; public ICollection Triangles => triangles; public ICollection Segments => subsegs.Values; public IEnumerable Edges { get { EdgeIterator e = new EdgeIterator(this); while (e.MoveNext()) { yield return e.Current; } } } public int NumberOfInputPoints => invertices; public int NumberOfEdges => (3 * triangles.Count + hullsize) / 2; public bool IsPolygon => insegments > 0; public NodeNumbering CurrentNumbering => numbering; private void Initialize() { dummysub = new SubSegment(); dummysub.hash = -1; dummysub.subsegs[0].seg = dummysub; dummysub.subsegs[1].seg = dummysub; dummytri = new Triangle(); dummytri.hash = (dummytri.id = -1); dummytri.neighbors[0].tri = dummytri; dummytri.neighbors[1].tri = dummytri; dummytri.neighbors[2].tri = dummytri; dummytri.subsegs[0].seg = dummysub; dummytri.subsegs[1].seg = dummysub; dummytri.subsegs[2].seg = dummysub; } 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; predicates = config.Predicates(); 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()); } qualityMesher.Apply(quality, delaunay); } public void Renumber() { Renumber(NodeNumbering.Linear); } public void Renumber(NodeNumbering num) { if (num == numbering) { return; } int num2; switch (num) { case NodeNumbering.Linear: num2 = 0; foreach (Vertex value in vertices.Values) { value.id = num2++; } break; case NodeNumbering.CuthillMcKee: { int[] array = new CuthillMcKee().Renumber(this); foreach (Vertex value2 in vertices.Values) { value2.id = array[value2.id]; } break; } } numbering = num; num2 = 0; foreach (Triangle triangle in triangles) { triangle.id = num2++; } } internal void SetQualityMesher(QualityMesher qmesher) { qualityMesher = qmesher; } internal void CopyTo(Mesh target) { target.vertices = vertices; target.triangles = triangles; target.subsegs = subsegs; target.holes = holes; target.regions = regions; target.hash_vtx = hash_vtx; target.hash_seg = hash_seg; target.hash_tri = hash_tri; target.numbering = numbering; target.hullsize = hullsize; } private void ResetData() { vertices.Clear(); triangles.Restart(); subsegs.Clear(); holes.Clear(); regions.Clear(); hash_vtx = 0; hash_seg = 0; hash_tri = 0; flipstack.Clear(); hullsize = 0; Reset(); locator.Reset(); } private void Reset() { numbering = NodeNumbering.None; undeads = 0; checksegments = false; checkquality = false; Statistic.InCircleCount = 0L; Statistic.CounterClockwiseCount = 0L; Statistic.InCircleAdaptCount = 0L; Statistic.CounterClockwiseAdaptCount = 0L; Statistic.Orient3dCount = 0L; Statistic.HyperbolaCount = 0L; Statistic.CircleTopCount = 0L; Statistic.CircumcenterCount = 0L; } internal void TransferNodes(IList points) { invertices = points.Count; mesh_dim = 2; bounds = new Rectangle(); if (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."); } Vertex vertex = points[0]; int num = nextras; nextras = num; bool flag = vertex.id != points[1].id; foreach (Vertex point in points) { if (flag) { point.hash = point.id; hash_vtx = Math.Max(point.hash + 1, hash_vtx); } else { point.hash = (point.id = hash_vtx++); } vertices.Add(point.hash, point); bounds.Expand(point); } } internal void MakeVertexMap() { Otri tri = default(Otri); foreach (Triangle triangle in triangles) { tri.tri = triangle; tri.orient = 0; while (tri.orient < 3) { tri.Org().tri = tri; tri.orient++; } } } internal void MakeTriangle(ref Otri newotri) { Triangle triangle = triangles.Get(); triangle.subsegs[0].seg = dummysub; triangle.subsegs[1].seg = dummysub; triangle.subsegs[2].seg = dummysub; triangle.neighbors[0].tri = dummytri; triangle.neighbors[1].tri = dummytri; triangle.neighbors[2].tri = dummytri; newotri.tri = triangle; newotri.orient = 0; } internal void MakeSegment(ref Osub newsubseg) { SubSegment subSegment = new SubSegment(); subSegment.hash = hash_seg++; subSegment.subsegs[0].seg = dummysub; subSegment.subsegs[1].seg = dummysub; subSegment.triangles[0].tri = dummytri; subSegment.triangles[1].tri = dummytri; newsubseg.seg = subSegment; newsubseg.orient = 0; subsegs.Add(subSegment.hash, subSegment); } internal InsertVertexResult InsertVertex(Vertex newvertex, ref Otri searchtri, ref Osub splitseg, bool segmentflaws, bool triflaws) { Otri ot = default(Otri); Otri ot2 = default(Otri); Otri ot3 = default(Otri); Otri ot4 = default(Otri); Otri ot5 = default(Otri); Otri ot6 = default(Otri); Otri newotri = default(Otri); Otri newotri2 = default(Otri); Otri newotri3 = default(Otri); Otri ot7 = default(Otri); Otri ot8 = default(Otri); Otri ot9 = default(Otri); Otri ot10 = default(Otri); Otri ot11 = default(Otri); Osub os = default(Osub); Osub os2 = default(Osub); Osub os3 = default(Osub); Osub os4 = default(Osub); Osub os5 = default(Osub); Osub os6 = default(Osub); Osub os7 = default(Osub); Osub os8 = default(Osub); LocateResult locateResult; if (splitseg.seg == null) { if (searchtri.tri.id == -1) { ot.tri = dummytri; ot.orient = 0; ot.Sym(); locateResult = locator.Locate(newvertex, ref ot); } else { searchtri.Copy(ref ot); locateResult = locator.PreciseLocate(newvertex, ref ot, stopatsubsegment: true); } } else { searchtri.Copy(ref ot); locateResult = LocateResult.OnEdge; } Vertex vertex3; Vertex vertex; Vertex vertex2; switch (locateResult) { case LocateResult.OnVertex: ot.Copy(ref searchtri); locator.Update(ref ot); return InsertVertexResult.Duplicate; case LocateResult.OnEdge: case LocateResult.Outside: { if (checksegments && splitseg.seg == null) { ot.Pivot(ref os5); if (os5.seg.hash != -1) { if (segmentflaws) { bool flag = behavior.NoBisect != 2; if (flag && behavior.NoBisect == 1) { ot.Sym(ref ot11); flag = ot11.tri.id != -1; } if (flag) { BadSubseg badSubseg = new BadSubseg(); badSubseg.subseg = os5; badSubseg.org = os5.Org(); badSubseg.dest = os5.Dest(); qualityMesher.AddBadSubseg(badSubseg); } } ot.Copy(ref searchtri); locator.Update(ref ot); return InsertVertexResult.Violating; } } ot.Lprev(ref ot4); ot4.Sym(ref ot8); ot.Sym(ref ot6); bool flag2 = ot6.tri.id != -1; if (flag2) { ot6.Lnext(); ot6.Sym(ref ot10); MakeTriangle(ref newotri3); } else { hullsize++; } MakeTriangle(ref newotri2); vertex = ot.Org(); vertex2 = ot.Dest(); vertex3 = ot.Apex(); newotri2.SetOrg(vertex3); newotri2.SetDest(vertex); newotri2.SetApex(newvertex); ot.SetOrg(newvertex); newotri2.tri.label = ot4.tri.label; if (behavior.VarArea) { newotri2.tri.area = ot4.tri.area; } if (flag2) { Vertex dest = ot6.Dest(); newotri3.SetOrg(vertex); newotri3.SetDest(dest); newotri3.SetApex(newvertex); ot6.SetOrg(newvertex); newotri3.tri.label = ot6.tri.label; if (behavior.VarArea) { newotri3.tri.area = ot6.tri.area; } } if (checksegments) { ot4.Pivot(ref os2); if (os2.seg.hash != -1) { ot4.SegDissolve(dummysub); newotri2.SegBond(ref os2); } if (flag2) { ot6.Pivot(ref os4); if (os4.seg.hash != -1) { ot6.SegDissolve(dummysub); newotri3.SegBond(ref os4); } } } newotri2.Bond(ref ot8); newotri2.Lprev(); newotri2.Bond(ref ot4); newotri2.Lprev(); if (flag2) { newotri3.Bond(ref ot10); newotri3.Lnext(); newotri3.Bond(ref ot6); newotri3.Lnext(); newotri3.Bond(ref newotri2); } if (splitseg.seg != null) { splitseg.SetDest(newvertex); Vertex segOrg = splitseg.SegOrg(); Vertex segDest = splitseg.SegDest(); splitseg.Sym(); splitseg.Pivot(ref os7); InsertSubseg(ref newotri2, splitseg.seg.boundary); newotri2.Pivot(ref os8); os8.SetSegOrg(segOrg); os8.SetSegDest(segDest); splitseg.Bond(ref os8); os8.Sym(); os8.Bond(ref os7); splitseg.Sym(); if (newvertex.label == 0) { newvertex.label = splitseg.seg.boundary; } } if (checkquality) { flipstack.Clear(); flipstack.Push(default(Otri)); flipstack.Push(ot); } ot.Lnext(); break; } default: ot.Lnext(ref ot3); ot.Lprev(ref ot4); ot3.Sym(ref ot7); ot4.Sym(ref ot8); MakeTriangle(ref newotri); MakeTriangle(ref newotri2); vertex = ot.Org(); vertex2 = ot.Dest(); vertex3 = ot.Apex(); newotri.SetOrg(vertex2); newotri.SetDest(vertex3); newotri.SetApex(newvertex); newotri2.SetOrg(vertex3); newotri2.SetDest(vertex); newotri2.SetApex(newvertex); ot.SetApex(newvertex); newotri.tri.label = ot.tri.label; newotri2.tri.label = ot.tri.label; if (behavior.VarArea) { double area = ot.tri.area; newotri.tri.area = area; newotri2.tri.area = area; } if (checksegments) { ot3.Pivot(ref os); if (os.seg.hash != -1) { ot3.SegDissolve(dummysub); newotri.SegBond(ref os); } ot4.Pivot(ref os2); if (os2.seg.hash != -1) { ot4.SegDissolve(dummysub); newotri2.SegBond(ref os2); } } newotri.Bond(ref ot7); newotri2.Bond(ref ot8); newotri.Lnext(); newotri2.Lprev(); newotri.Bond(ref newotri2); newotri.Lnext(); ot3.Bond(ref newotri); newotri2.Lprev(); ot4.Bond(ref newotri2); if (checkquality) { flipstack.Clear(); flipstack.Push(ot); } break; } InsertVertexResult result = InsertVertexResult.Successful; if (newvertex.tri.tri != null) { newvertex.tri.SetOrg(vertex); newvertex.tri.SetDest(vertex2); newvertex.tri.SetApex(vertex3); } Vertex vertex4 = ot.Org(); vertex = vertex4; vertex2 = ot.Dest(); while (true) { bool flag3 = true; if (checksegments) { ot.Pivot(ref os6); if (os6.seg.hash != -1) { flag3 = false; if (segmentflaws && qualityMesher.CheckSeg4Encroach(ref os6) > 0) { result = InsertVertexResult.Encroaching; } } } if (flag3) { ot.Sym(ref ot2); if (ot2.tri.id == -1) { flag3 = false; } else { Vertex vertex5 = ot2.Apex(); flag3 = ((!(vertex2 == infvertex1) && !(vertex2 == infvertex2) && !(vertex2 == infvertex3)) ? ((!(vertex == infvertex1) && !(vertex == infvertex2) && !(vertex == infvertex3)) ? (!(vertex5 == infvertex1) && !(vertex5 == infvertex2) && !(vertex5 == infvertex3) && predicates.InCircle(vertex2, newvertex, vertex, vertex5) > 0.0) : (predicates.CounterClockwise(vertex5, vertex2, newvertex) > 0.0)) : (predicates.CounterClockwise(newvertex, vertex, vertex5) > 0.0)); if (flag3) { ot2.Lprev(ref ot5); ot5.Sym(ref ot9); ot2.Lnext(ref ot6); ot6.Sym(ref ot10); ot.Lnext(ref ot3); ot3.Sym(ref ot7); ot.Lprev(ref ot4); ot4.Sym(ref ot8); ot5.Bond(ref ot7); ot3.Bond(ref ot8); ot4.Bond(ref ot10); ot6.Bond(ref ot9); if (checksegments) { ot5.Pivot(ref os3); ot3.Pivot(ref os); ot4.Pivot(ref os2); ot6.Pivot(ref os4); if (os3.seg.hash == -1) { ot6.SegDissolve(dummysub); } else { ot6.SegBond(ref os3); } if (os.seg.hash == -1) { ot5.SegDissolve(dummysub); } else { ot5.SegBond(ref os); } if (os2.seg.hash == -1) { ot3.SegDissolve(dummysub); } else { ot3.SegBond(ref os2); } if (os4.seg.hash == -1) { ot4.SegDissolve(dummysub); } else { ot4.SegBond(ref os4); } } ot.SetOrg(vertex5); ot.SetDest(newvertex); ot.SetApex(vertex); ot2.SetOrg(newvertex); ot2.SetDest(vertex5); ot2.SetApex(vertex2); int label = Math.Min(ot2.tri.label, ot.tri.label); ot2.tri.label = label; ot.tri.label = label; if (behavior.VarArea) { double area = ((!(ot2.tri.area <= 0.0) && !(ot.tri.area <= 0.0)) ? (0.5 * (ot2.tri.area + ot.tri.area)) : (-1.0)); ot2.tri.area = area; ot.tri.area = area; } if (checkquality) { flipstack.Push(ot); } ot.Lprev(); vertex2 = vertex5; } } } if (!flag3) { if (triflaws) { qualityMesher.TestTriangle(ref ot); } ot.Lnext(); ot.Sym(ref ot11); if (vertex2 == vertex4 || ot11.tri.id == -1) { break; } ot11.Lnext(ref ot); vertex = vertex2; vertex2 = ot.Dest(); } } ot.Lnext(ref searchtri); Otri ot12 = default(Otri); ot.Lnext(ref ot12); locator.Update(ref ot12); return result; } internal void InsertSubseg(ref Otri tri, int subsegmark) { Otri ot = default(Otri); Osub os = default(Osub); Vertex vertex = tri.Org(); Vertex vertex2 = tri.Dest(); if (vertex.label == 0) { vertex.label = subsegmark; } if (vertex2.label == 0) { vertex2.label = subsegmark; } tri.Pivot(ref os); if (os.seg.hash == -1) { MakeSegment(ref os); os.SetOrg(vertex2); os.SetDest(vertex); os.SetSegOrg(vertex2); os.SetSegDest(vertex); tri.SegBond(ref os); tri.Sym(ref ot); os.Sym(); ot.SegBond(ref os); os.seg.boundary = subsegmark; } else if (os.seg.boundary == 0) { os.seg.boundary = subsegmark; } } internal void Flip(ref Otri flipedge) { Otri ot = default(Otri); Otri ot2 = default(Otri); Otri ot3 = default(Otri); Otri ot4 = default(Otri); Otri ot5 = default(Otri); Otri ot6 = default(Otri); Otri ot7 = default(Otri); Otri ot8 = default(Otri); Otri ot9 = default(Otri); Osub os = default(Osub); Osub os2 = default(Osub); Osub os3 = default(Osub); Osub os4 = default(Osub); Vertex apex = flipedge.Org(); Vertex apex2 = flipedge.Dest(); Vertex vertex = flipedge.Apex(); flipedge.Sym(ref ot5); Vertex vertex2 = ot5.Apex(); ot5.Lprev(ref ot3); ot3.Sym(ref ot8); ot5.Lnext(ref ot4); ot4.Sym(ref ot9); flipedge.Lnext(ref ot); ot.Sym(ref ot6); flipedge.Lprev(ref ot2); ot2.Sym(ref ot7); ot3.Bond(ref ot6); ot.Bond(ref ot7); ot2.Bond(ref ot9); ot4.Bond(ref ot8); if (checksegments) { ot3.Pivot(ref os3); ot.Pivot(ref os); ot2.Pivot(ref os2); ot4.Pivot(ref os4); if (os3.seg.hash == -1) { ot4.SegDissolve(dummysub); } else { ot4.SegBond(ref os3); } if (os.seg.hash == -1) { ot3.SegDissolve(dummysub); } else { ot3.SegBond(ref os); } if (os2.seg.hash == -1) { ot.SegDissolve(dummysub); } else { ot.SegBond(ref os2); } if (os4.seg.hash == -1) { ot2.SegDissolve(dummysub); } else { ot2.SegBond(ref os4); } } flipedge.SetOrg(vertex2); flipedge.SetDest(vertex); flipedge.SetApex(apex); ot5.SetOrg(vertex); ot5.SetDest(vertex2); ot5.SetApex(apex2); } internal void Unflip(ref Otri flipedge) { Otri ot = default(Otri); Otri ot2 = default(Otri); Otri ot3 = default(Otri); Otri ot4 = default(Otri); Otri ot5 = default(Otri); Otri ot6 = default(Otri); Otri ot7 = default(Otri); Otri ot8 = default(Otri); Otri ot9 = default(Otri); Osub os = default(Osub); Osub os2 = default(Osub); Osub os3 = default(Osub); Osub os4 = default(Osub); Vertex apex = flipedge.Org(); Vertex apex2 = flipedge.Dest(); Vertex vertex = flipedge.Apex(); flipedge.Sym(ref ot5); Vertex vertex2 = ot5.Apex(); ot5.Lprev(ref ot3); ot3.Sym(ref ot8); ot5.Lnext(ref ot4); ot4.Sym(ref ot9); flipedge.Lnext(ref ot); ot.Sym(ref ot6); flipedge.Lprev(ref ot2); ot2.Sym(ref ot7); ot3.Bond(ref ot9); ot.Bond(ref ot8); ot2.Bond(ref ot6); ot4.Bond(ref ot7); if (checksegments) { ot3.Pivot(ref os3); ot.Pivot(ref os); ot2.Pivot(ref os2); ot4.Pivot(ref os4); if (os3.seg.hash == -1) { ot.SegDissolve(dummysub); } else { ot.SegBond(ref os3); } if (os.seg.hash == -1) { ot2.SegDissolve(dummysub); } else { ot2.SegBond(ref os); } if (os2.seg.hash == -1) { ot4.SegDissolve(dummysub); } else { ot4.SegBond(ref os2); } if (os4.seg.hash == -1) { ot3.SegDissolve(dummysub); } else { ot3.SegBond(ref os4); } } flipedge.SetOrg(vertex); flipedge.SetDest(vertex2); flipedge.SetApex(apex2); ot5.SetOrg(vertex2); ot5.SetDest(vertex); ot5.SetApex(apex); } private void TriangulatePolygon(Otri firstedge, Otri lastedge, int edgecount, bool doflip, bool triflaws) { Otri ot = default(Otri); Otri ot2 = default(Otri); Otri ot3 = default(Otri); int num = 1; Vertex a = lastedge.Apex(); Vertex b = firstedge.Dest(); firstedge.Onext(ref ot2); Vertex c = ot2.Dest(); ot2.Copy(ref ot); for (int i = 2; i <= edgecount - 2; i++) { ot.Onext(); Vertex vertex = ot.Dest(); if (predicates.InCircle(a, b, c, vertex) > 0.0) { ot.Copy(ref ot2); c = vertex; num = i; } } if (num > 1) { ot2.Oprev(ref ot3); TriangulatePolygon(firstedge, ot3, num + 1, doflip: true, triflaws); } if (num < edgecount - 2) { ot2.Sym(ref ot3); TriangulatePolygon(ot2, lastedge, edgecount - num, doflip: true, triflaws); ot3.Sym(ref ot2); } if (doflip) { Flip(ref ot2); if (triflaws) { ot2.Sym(ref ot); qualityMesher.TestTriangle(ref ot); } } ot2.Copy(ref lastedge); } internal void DeleteVertex(ref Otri deltri) { Otri ot = default(Otri); Otri ot2 = default(Otri); Otri ot3 = default(Otri); Otri ot4 = default(Otri); Otri ot5 = default(Otri); Otri ot6 = default(Otri); Otri ot7 = default(Otri); Otri ot8 = default(Otri); Osub os = default(Osub); Osub os2 = default(Osub); Vertex dyingvertex = deltri.Org(); VertexDealloc(dyingvertex); deltri.Onext(ref ot); int num = 1; while (!deltri.Equals(ot)) { num++; ot.Onext(); } if (num > 3) { deltri.Onext(ref ot2); deltri.Oprev(ref ot3); TriangulatePolygon(ot2, ot3, num, doflip: false, behavior.NoBisect == 0); } deltri.Lprev(ref ot4); deltri.Dnext(ref ot5); ot5.Sym(ref ot7); ot4.Oprev(ref ot6); ot6.Sym(ref ot8); deltri.Bond(ref ot7); ot4.Bond(ref ot8); ot5.Pivot(ref os); if (os.seg.hash != -1) { deltri.SegBond(ref os); } ot6.Pivot(ref os2); if (os2.seg.hash != -1) { ot4.SegBond(ref os2); } Vertex org = ot5.Org(); deltri.SetOrg(org); if (behavior.NoBisect == 0) { qualityMesher.TestTriangle(ref deltri); } TriangleDealloc(ot5.tri); TriangleDealloc(ot6.tri); } internal void UndoVertex() { Otri ot = default(Otri); Otri ot2 = default(Otri); Otri ot3 = default(Otri); Otri ot4 = default(Otri); Otri ot5 = default(Otri); Otri ot6 = default(Otri); Otri ot7 = default(Otri); Osub os = default(Osub); Osub os2 = default(Osub); Osub os3 = default(Osub); while (flipstack.Count > 0) { Otri flipedge = flipstack.Pop(); if (flipstack.Count == 0) { flipedge.Dprev(ref ot); ot.Lnext(); flipedge.Onext(ref ot2); ot2.Lprev(); ot.Sym(ref ot4); ot2.Sym(ref ot5); Vertex apex = ot.Dest(); flipedge.SetApex(apex); flipedge.Lnext(); flipedge.Bond(ref ot4); ot.Pivot(ref os); flipedge.SegBond(ref os); flipedge.Lnext(); flipedge.Bond(ref ot5); ot2.Pivot(ref os2); flipedge.SegBond(ref os2); TriangleDealloc(ot.tri); TriangleDealloc(ot2.tri); } else if (flipstack.Peek().tri == null) { flipedge.Lprev(ref ot7); ot7.Sym(ref ot2); ot2.Lnext(); ot2.Sym(ref ot5); Vertex org = ot2.Dest(); flipedge.SetOrg(org); ot7.Bond(ref ot5); ot2.Pivot(ref os2); ot7.SegBond(ref os2); TriangleDealloc(ot2.tri); flipedge.Sym(ref ot7); if (ot7.tri.id != -1) { ot7.Lnext(); ot7.Dnext(ref ot3); ot3.Sym(ref ot6); ot7.SetOrg(org); ot7.Bond(ref ot6); ot3.Pivot(ref os3); ot7.SegBond(ref os3); TriangleDealloc(ot3.tri); } flipstack.Clear(); } else { Unflip(ref flipedge); } } } internal void TriangleDealloc(Triangle dyingtriangle) { Otri.Kill(dyingtriangle); triangles.Release(dyingtriangle); } internal void VertexDealloc(Vertex dyingvertex) { dyingvertex.type = VertexType.DeadVertex; vertices.Remove(dyingvertex.hash); } internal void SubsegDealloc(SubSegment dyingsubseg) { Osub.Kill(dyingsubseg); subsegs.Remove(dyingsubseg.hash); } } }