/// <summary> /// Split an edge with <paramref name="vertexIndex"/>. /// For each half edge two new triangles replace old one. /// </summary> /// <param name="vertexIndex">Vertex index.</param> /// <param name="edge">Half edge.</param> private void SplitEdge(int vertexIndex, HalfEdge edge) { var twin = edge.Twin; var edgesToCheck = new List <HalfEdge>(12); SplitHalfEdge(edge); if (twin != null) { SplitHalfEdge(twin); var twinSplitNext = twin.Next.Twin.Next; twin.TwinWith(edge.Next.Twin.Next); twinSplitNext.TwinWith(edge); } edge.Constrained = edge.Constrained; edge.Next.Twin.Next.Constrained = edge.Constrained; RestoreDelaunayProperty(edgesToCheck); void SplitHalfEdge(HalfEdge e) { var next = e.Next; var prev = next.Next; // Form first triangle var split = new HalfEdge(vertexIndex) { Next = prev, }; e.Next = split; // Form second triangle var splitTwin = new HalfEdge(prev.Origin); var splitTwinNext = new HalfEdge(vertexIndex) { Next = next, }; splitTwin.Next = splitTwinNext; next.Next = splitTwin; splitTwin.TwinWith(split); edgesToCheck.Add(e); edgesToCheck.Add(split); edgesToCheck.Add(prev); edgesToCheck.Add(splitTwin); edgesToCheck.Add(splitTwinNext); edgesToCheck.Add(next); } }
private HalfEdge Flip(HalfEdge edge) { var twin = edge.Twin; var edgeNext = edge.Next; var twinNext = twin.Next; edge.Prev.Next = twinNext; twin.Prev.Next = edgeNext; edge.Detach(); twin.Detach(); edge = new HalfEdge(edgeNext.Next.Origin) { Next = twinNext.Next }; twin = new HalfEdge(twinNext.Next.Origin) { Next = edgeNext.Next }; edge.TwinWith(twin); edgeNext.Next = edge; twinNext.Next = twin; return(edge); }
private void ToConvexHull(HalfEdge borderEdge = null) { var current = borderEdge; if (current == null) { foreach (var halfEdge in HalfEdges) { if (halfEdge.Twin == null) { current = halfEdge; break; } } } var edgesToCheck = new List <HalfEdge>(); var prev = PrevBorderEdge(current); var start = current; // Same as Ear clipping method but for 'outside' ears. do { var o1 = prev.Origin; var o2 = current.Origin; var o3 = current.Next.Origin; var p1 = InnerVertices[o1].Pos; var p2 = InnerVertices[o2].Pos; var p3 = InnerVertices[o3].Pos; if (!AreClockwiseOrderedOrCollinear(p1, p2, p3)) { var c = start; var intersectAny = false; do { if (c.Origin == o1 || c.Origin == o2 || c.Origin == o3 || c.Next.Origin == o1) { c = NextBorderEdge(c); continue; } var s1 = InnerVertices[c.Origin].Pos; var s2 = InnerVertices[c.Next.Origin].Pos; if (RobustSegmentSegmentIntersection(p1, p3, s1, s2)) { intersectAny = true; break; } c = NextBorderEdge(c); } while (start != c); if (!intersectAny) { var t = new HalfEdge(current.Next.Origin) { Next = new HalfEdge(current.Origin) { Next = new HalfEdge(prev.Origin), }, }; edgesToCheck.Add(t.Prev); t.Prev.Next = t; t.TwinWith(current); t.Next.TwinWith(prev); start = current = t.Prev; prev = PrevBorderEdge(start); continue; } } prev = current; current = NextBorderEdge(current); if (current == start) { break; } } while (true); RestoreDelaunayProperty(edgesToCheck); }
private bool InnerInsertConstrainedEdge(int index0, int index1, bool destroyConstrained = false) { var location = LocateClosestTriangle(index0, out var start); System.Diagnostics.Debug.Assert(location == LocationResult.SameVertex); System.Diagnostics.Debug.Assert(start.Origin == index0); // Check test cases for explanation var upperUsed = new HashSet <int>(); var lowerUsed = new HashSet <int>(); var shouldBeReinserted = new HashSet <int>(); var shouldBeReconstrained = new List <(int, int)>(); var a = InnerVertices[index0].Pos; var b = InnerVertices[index1].Pos; var ab = b - a; var signab = new IntVector2(Mathf.Sign(ab.X), Mathf.Sign(ab.Y)); // In order to insert a constrain edge we have to delete all // triangles that (index0, index1) crosses. Thereafter we // have 2 polygonal holes that should be triangulated. List <HalfEdge> upperPolygon = null, lowerPolygon = null; var current = start; foreach (var incidentEdge in IncidentEdges(start)) { var next = incidentEdge.Next; var prev = next.Next; // Special case: requested edge already exists. if (next.Origin == index1) { return(incidentEdge.Constrained = true); } if (prev.Origin == index1) { return(prev.Constrained = true); } var c = InnerVertices[next.Origin].Pos; var d = InnerVertices[prev.Origin].Pos; var e = InnerVertices[incidentEdge.Origin].Pos; // Special case: requested edge lies on the same line as [e, c] or [e, d]. // If true then mark edge as constrained and insert edge [next.Origin, index1] or [prev.Origin, index1]. if (IsVertexOnLine(a, e, c) && IsVertexOnLine(b, e, c)) { var ec = c - e; // Check for co-directionality in order to prevent looping if (Mathf.Sign(ec.X) == signab.X && Mathf.Sign(ec.Y) == signab.Y) { return(incidentEdge.Constrained = InnerInsertConstrainedEdge(next.Origin, index1, destroyConstrained)); } } else if (IsVertexOnLine(a, e, d) && IsVertexOnLine(b, e, d)) { var ed = d - e; // Check for co-directionality in order to prevent looping if (Mathf.Sign(ed.X) == signab.X && Mathf.Sign(ed.Y) == signab.Y) { return(prev.Constrained = InnerInsertConstrainedEdge(prev.Origin, index1, destroyConstrained)); } } else if (RobustSegmentSegmentIntersection(a, b, c, d)) { // Should not delete existing constrain edges. if (next.Constrained && !destroyConstrained) { return(false); } // Then we should start traversing upperPolygon = new List <HalfEdge> { incidentEdge }; lowerPolygon = new List <HalfEdge> { prev }; lowerUsed.Add(prev.Origin); upperUsed.Add(incidentEdge.Origin); current = next; break; } } while (true) { if (current.Twin == null) { // Something horrible has happened.. // Something like trying to connect two vertices trough concavity. // Should not happen at all in current implementation. return(false); } current = current.Twin; var next = current.Next; var prev = next.Next; if (prev.Origin == index1) { // We found the end of the requested edge. // Stop traversing and triangulate both polygons. AddLower(prev); AddUpper(next); Finish(index0, index1); return(true); } var c = InnerVertices[next.Origin].Pos; var d = InnerVertices[prev.Origin].Pos; var e = InnerVertices[current.Origin].Pos; if (IsVertexOnLine(d, a, b)) { // Special case: [a, b] intersects triangle exactly in the // vertex that is opposite to basis (`current` edge). // Should finish traversing and insert constrain edge // between prev.Origin and index1. AddLower(prev); AddUpper(next); var edge = Finish(index0, prev.Origin); edge.Constrained = InnerInsertConstrainedEdge(prev.Origin, index1, destroyConstrained); RestoreDelaunayProperty(upperPolygon); RestoreDelaunayProperty(lowerPolygon); return(edge.Constrained); } if (RobustSegmentSegmentIntersection(a, b, c, d)) { if (next.Constrained && !destroyConstrained) { return(false); } AddLower(prev); current = next; } else if (RobustSegmentSegmentIntersection(a, b, d, e)) { if (prev.Constrained && !destroyConstrained) { return(false); } AddUpper(next); current = prev; } else { System.Diagnostics.Debug.Fail("This is impossible case."); } } void AddUpper(HalfEdge e) { if (upperUsed.Add(e.Origin)) { upperPolygon.Add(e); } else { // It means that triangles from i to polygon.Count should be removed. // Removing those triangles makes some vertices isolated and // also may remove previously inserted constrained edges. // So we keep that in mind in order to reinsert missing elements later. var i = upperPolygon.FindIndex(edge => edge.Origin == e.Origin); for (int j = upperPolygon.Count - 1; j >= i; j--) { var t = upperPolygon[j]; shouldBeReinserted.Add(t.Origin); shouldBeReinserted.Add(t.Next.Origin); if (t.Constrained) { shouldBeReconstrained.Add((t.Origin, t.Next.Origin)); } upperPolygon.RemoveAt(j); } upperPolygon.Add(e); } } void AddLower(HalfEdge e) { if (lowerUsed.Add(e.Origin)) { lowerPolygon.Add(e); } else { // Same as for upper polygon except that it is filled in reverse order. var i = lowerPolygon.FindIndex(edge => edge.Origin == e.Origin); for (int j = lowerPolygon.Count - 1; j > i; j--) { var t = lowerPolygon[j]; shouldBeReinserted.Add(t.Origin); shouldBeReinserted.Add(t.Next.Origin); if (t.Constrained) { shouldBeReconstrained.Add((t.Origin, t.Next.Origin)); } lowerPolygon.RemoveAt(j); } shouldBeReinserted.Add(e.Next.Origin); } } HalfEdge Finish(int i0, int i1) { // Create (i0, i1) half edges. var e1 = new HalfEdge(i1); var e2 = new HalfEdge(i0); e1.TwinWith(e2); e1.Constrained = true; AddUpper(e1); AddLower(e2); lowerPolygon.Reverse(); // Triangulate both polygonal holes. TriangulatePolygonByEarClipping(upperPolygon, false); TriangulatePolygonByEarClipping(lowerPolygon, false); RestoreDelaunayProperty(upperPolygon); // Reinsert missing elements. foreach (var vertexIndex in shouldBeReinserted) { AddVertex(vertexIndex); } foreach (var edge in shouldBeReconstrained) { InnerInsertConstrainedEdge(edge.Item1, edge.Item2); } return(e2); } }