/// <summary> /// Given a initial vertex and a new point, finds the face that is split by a new edge in this direction /// </summary> /// <param name="a_vertex"></param> /// <param name="a_point"></param> /// <returns></returns> private Face GetSplittingFace(DCELVertex a_vertex, Vector2 a_point) { List <HalfEdge> outedges = OutgoingEdges(a_vertex).ToList(); if (outedges.Count == 0) { // a_Vertex1 leaving is null throw new GeomException("Vertex should be connected to a face boundary"); } outedges.Sort(EdgeAngleComparer); foreach (var curEdge in outedges) { var angle = MathUtil.Angle(a_vertex.Pos, a_vertex.Pos + new Vector2(1f, 0f), curEdge.To.Pos); var angle2 = MathUtil.Angle(a_vertex.Pos, a_vertex.Pos + new Vector2(1f, 0f), a_point); if (angle >= angle2) { return(curEdge.Face); } } return(outedges.FirstOrDefault().Face); }
/// <summary> /// Fix the edge chaining from the given vertex and insert the half edge. /// Updates the next/prev data from the adjacent edges of a_Vertex /// to include the new edge. /// </summary> /// <param name="a_Vertex"></param> /// <param name="a_Edge"></param> private void AddEdgeInVertexChain(DCELVertex a_Vertex, HalfEdge a_Edge) { List <HalfEdge> outedges = OutgoingEdges(a_Vertex).ToList(); if (outedges.Count == 0) { // Add initial edge to vertex Chain(a_Edge.Twin, a_Edge); a_Vertex.Leaving = a_Edge; return; } outedges.Sort(EdgeAngleComparer); // loop over edges in order of angle for (int i = 0; i < outedges.Count; i++) { var curEdge = outedges[i]; var angle = MathUtil.Angle(curEdge.From.Pos, curEdge.From.Pos + new Vector2(1f, 0f), curEdge.To.Pos); var angle2 = MathUtil.Angle(a_Edge.From.Pos, a_Edge.From.Pos + new Vector2(1f, 0f), a_Edge.To.Pos); if (angle >= angle2) { // chain new edge correctly var prevEdge = outedges[MathUtil.PositiveMod(i - 1, outedges.Count)]; Chain(a_Edge.Twin, curEdge); Chain(prevEdge.Twin, a_Edge); return; } } // new edge between first and last edge Chain(a_Edge.Twin, outedges.FirstOrDefault()); Chain(outedges.Last().Twin, a_Edge); }
/// <summary> /// /// </summary> /// <param name="a_Edge"></param> /// <param name="a_Vertex"></param> /// <returns> The inserted Vertex. /// If the requested insertion Vertex is on a endpoint we insert no vertex /// and instead return said endpoint ///</returns> public DCELVertex AddVertexInEdge(HalfEdge a_Edge, Vector2 a_Point) { if (!m_Edges.Contains(a_Edge)) { throw new GeomException("Edge should already be part of DCEL"); } if (!a_Edge.Segment.IsOnSegment(a_Point)) { throw new GeomException("Point should lie on edge"); } if (MathUtil.EqualsEpsVertex(a_Edge.From.Pos, a_Point)) { return(a_Edge.From); //throw new GeomException("Requested insertion in Edge on From.Pos"); } if (MathUtil.EqualsEpsVertex(a_Edge.To.Pos, a_Point)) { return(a_Edge.To); //throw new GeomException("Requested insertion in Edge on To.Pos"); } // create vertex with outgoing edge var a_Vertex = new DCELVertex(a_Point, a_Edge.Twin); m_Vertices.AddLast(a_Vertex); // update old edge pointers var oldTo = a_Edge.To; a_Edge.To = a_Vertex; a_Edge.Twin.From = a_Vertex; // create new halfedges var newedge = new HalfEdge(a_Vertex, oldTo); var newtwinedge = new HalfEdge(oldTo, a_Vertex); m_Edges.AddLast(newedge); m_Edges.AddLast(newtwinedge); Twin(newedge, newtwinedge); // fix next/prev pointers in the original cycle Chain(newedge, a_Edge.Next); Chain(a_Edge, newedge); // fix pointers in the twin cycle Chain(a_Edge.Twin.Prev, newtwinedge); Chain(newtwinedge, a_Edge.Twin); // set faces newedge.Face = a_Edge.Face; newtwinedge.Face = a_Edge.Twin.Face; // update old leaving edge as it may point to the wrong edge now oldTo.Leaving = newtwinedge; return(a_Vertex); }
/// <summary> /// Find all half edges adjacent to the given vertex, ingoing as well as outgoing. /// </summary> /// <param name="a_Vertex1"></param> /// <returns></returns> public List <HalfEdge> AdjacentEdges(DCELVertex a_Vertex1) { var edges = new List <HalfEdge>(); foreach (var e in OutgoingEdges(a_Vertex1)) { edges.Add(e); edges.Add(e.Twin); } return(edges); }
/// <summary> /// Adds a given DCELVertex to the DCEL. /// </summary> /// <param name="a_Vertex"></param> /// <returns>The added vertex</returns> public DCELVertex AddVertex(DCELVertex a_Vertex) { HalfEdge a_Edge; if (OnEdge(a_Vertex, out a_Edge)) { return(AddVertexInEdge(a_Edge, a_Vertex.Pos)); } else { m_Vertices.AddLast(a_Vertex); return(a_Vertex); } }
/// <summary> /// Returns all edges that are outgoing from the given vertex. /// </summary> /// <param name="a_Vertex1"></param> /// <returns></returns> public List <HalfEdge> OutgoingEdges(DCELVertex a_Vertex1) { if (a_Vertex1.Leaving == null) { return(new List <HalfEdge>()); } var edges = new List <HalfEdge>(); var e = a_Vertex1.Leaving; do { edges.Add(e); e = e.Twin.Next; } while (e != a_Vertex1.Leaving); return(edges); }
/// <summary> /// Checks whether the given vertex lies on the cycle /// </summary> /// <param name="a_Vertex"></param> /// <param name="a_Edge"></param> /// <returns></returns> private bool OnEdge(DCELVertex a_Vertex, out HalfEdge a_Edge) { a_Edge = m_Edges.FirstOrDefault(e => e.Segment.IsOnSegment(a_Vertex.Pos)); return(a_Edge != null); }
/// <summary> /// Checks whether the given vertex lies on the edge cycle specified by the halfedge. /// </summary> /// <param name="a_startedge"></param> /// <param name="a_Vertex"></param> /// <returns></returns> private static bool OnCycle(HalfEdge a_startedge, DCELVertex a_Vertex) { return(Cycle(a_startedge).ToList().Exists(e => e.To == a_Vertex)); }
/// <summary> /// Finds a vertex with the given location, or null otherwise. /// </summary> /// <remarks> /// Slow method O(n), not recommended. /// </remarks> /// <param name="a_Point"></param> /// <param name="a_Vertex"></param> /// <returns></returns> public bool FindVertex(Vector2 a_Point, out DCELVertex a_Vertex) { a_Vertex = m_Vertices.FirstOrDefault(v => a_Point.Equals(v.Pos)); return(a_Vertex != null); }
/// <summary> /// Adds an edge, consisting of two halfedges, between existing vertices in the DCEL. /// </summary> /// <remarks> /// Vertices should be adjacent to a common face. /// </remarks> /// <param name="a_vertex1"></param> /// <param name="a_vertex2"></param> /// <returns> One of the newly added edges </returns> public HalfEdge AddEdge(DCELVertex a_Vertex1, DCELVertex a_Vertex2) { if (!m_Vertices.Contains(a_Vertex1) || !m_Vertices.Contains(a_Vertex2)) { throw new GeomException("Vertices should already be part of the DCEL"); } // create edges var e1 = new HalfEdge(a_Vertex1, a_Vertex2); var e2 = new HalfEdge(a_Vertex2, a_Vertex1); m_Edges.AddLast(e1); m_Edges.AddLast(e2); bool newFace = false; Face face1, face2; // check if both vertices already part of face // or disconnected inside a face if (OutgoingEdges(a_Vertex1).Count() != 0 && OutgoingEdges(a_Vertex2).Count() != 0) { // get faces split by new edge from each vertex face1 = GetSplittingFace(a_Vertex1, a_Vertex2.Pos); face2 = GetSplittingFace(a_Vertex2, a_Vertex1.Pos); // check if new edge will create additional face var newInnerFace = face1.InnerComponents.Exists(e => OnCycle(e, a_Vertex1) && OnCycle(e, a_Vertex2)); var outerFaceSplit = !face1.IsOuter && OnCycle(face1.OuterComponent, a_Vertex1) && OnCycle(face1.OuterComponent, a_Vertex2); newFace = newInnerFace || outerFaceSplit; } else if (OutgoingEdges(a_Vertex2).Count() != 0) { face1 = GetContainingFace(a_Vertex1.Pos); face2 = GetSplittingFace(a_Vertex2, a_Vertex1.Pos); } else if (OutgoingEdges(a_Vertex1).Count() != 0) { face1 = GetSplittingFace(a_Vertex1, a_Vertex2.Pos); face2 = GetContainingFace(a_Vertex2.Pos); } else { face1 = GetContainingFace(a_Vertex1.Pos); face2 = GetContainingFace(a_Vertex2.Pos); // new inner component inside face face1.InnerComponents.Add(e1); } if (face1 != face2) { throw new GeomException("Vertices do not lie in the same face:\n" + a_Vertex1 + "\n" + a_Vertex2 + "\n" + face1 + "\n" + face2); } // fix edge pointers e1.Face = face1; e2.Face = face1; Twin(e1, e2); AddEdgeInVertexChain(a_Vertex1, e1); AddEdgeInVertexChain(a_Vertex2, e2); if (newFace) { face2 = SplitFace(e1, e2, face1); } else { face2 = null; } // check whether inner component has become part of outer component // or inner components have merged FixInnerComponents(e1, e2, face1, face2); return(e1); }