// This makes a polygon from the path public EarClipPolygon MakePolygon(bool startfront) { EarClipPolygon p = new EarClipPolygon(); bool forward = startfront; // Any sides at all? if (Count > 0) { p.AddLast(forward ? new EarClipVertex(base[0].Start.Position, base[0].Front) : new EarClipVertex(base[0].End.Position, base[0].Back)); // Add all lines, but the first for (int i = 1; i < Count; i++) { // Traverse direction changes? if ((base[i - 1].Start == base[i].Start) || (base[i - 1].End == base[i].End)) { forward = !forward; } // Add next vertex p.AddLast(forward ? new EarClipVertex(base[i].Start.Position, base[i].Front) : new EarClipVertex(base[i].End.Position, base[i].Back)); } } return(p); }
// This inserts a polygon if it is a child of this one public bool InsertChild(EarClipPolygon p) { // Polygon must have at least 1 vertex if (p.Count == 0) { return(false); } // Check if it can be inserted at a lower level foreach (EarClipPolygon child in children) { if (child.InsertChild(p)) { return(true); } } // Check if it can be inserted here if (this.Intersect(p.First.Value.Position)) { // Make the polygon the inverse of this one p.Inner = !inner; children.Add(p); return(true); } // Can't insert it as a child return(false); }
// This cuts into outer polygons to solve inner polygons and make the polygon tree flat private static void DoCutting(List <EarClipPolygon> polys) { Queue <EarClipPolygon> todo = new Queue <EarClipPolygon>(polys); // Begin processing outer polygons while (todo.Count > 0) { // Get outer polygon to process EarClipPolygon p = todo.Dequeue(); // Any inner polygons to work with? if (p.Children.Count > 0) { // Go for all the children foreach (EarClipPolygon c in p.Children) { // The children of the children are outer polygons again, // so move them to the root and add for processing polys.AddRange(c.Children); foreach (EarClipPolygon sc in c.Children) { todo.Enqueue(sc); } // Remove from inner polygon c.Children.Clear(); } // Now do some cutting on this polygon to merge the inner polygons MergeInnerPolys(p); } } }
// This finds the right-most vertex in an inner polygon to use for cut startpoint. private static LinkedListNode <EarClipVertex> FindRightMostVertex(EarClipPolygon p) { LinkedListNode <EarClipVertex> found = p.First; LinkedListNode <EarClipVertex> v = found.Next; // Go for all vertices to find the on with the biggest x value while (v != null) { if (v.Value.Position.x > found.Value.Position.x) { found = v; } v = v.Next; } // Return result return(found); }
// This takes an outer polygon and a set of inner polygons to start cutting on private static void MergeInnerPolys(EarClipPolygon p) { LinkedList <EarClipPolygon> todo = new LinkedList <EarClipPolygon>(p.Children); LinkedListNode <EarClipVertex> start; LinkedListNode <EarClipPolygon> ip; LinkedListNode <EarClipPolygon> found; LinkedListNode <EarClipVertex> foundstart; // Continue until no more inner polygons to process while (todo.Count > 0) { // Find the inner polygon with the highest x vertex found = null; foundstart = null; ip = todo.First; while (ip != null) { start = FindRightMostVertex(ip.Value); if ((foundstart == null) || (start.Value.Position.x > foundstart.Value.Position.x)) { // Found a better start found = ip; foundstart = start; } // Next! ip = ip.Next; } // Remove from to do list todo.Remove(found); // Get cut start and end SplitOuterWithInner(foundstart, p); } // Remove the children, they should be merged in the polygon by now p.Children.Clear(); }
// This makes a polygon from the path public EarClipPolygon MakePolygon() { EarClipPolygon p = new EarClipPolygon(); // Any sides at all? if (Count > 0) { // Add all sides for (int i = 0; i < Count; i++) { // On front or back? if (base[i].IsFront) { p.AddLast(new EarClipVertex(base[i].Line.End.Position, base[i])); } else { p.AddLast(new EarClipVertex(base[i].Line.Start.Position, base[i])); } } } return(p); }
// This clips a polygon and returns the triangles // The polygon may not have any holes or islands // See: http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf private int DoEarClip(EarClipPolygon poly, List <Vector2D> verticeslist, List <Sidedef> sidedefslist) { LinkedList <EarClipVertex> verts = new LinkedList <EarClipVertex>(); List <EarClipVertex> convexes = new List <EarClipVertex>(poly.Count); LinkedList <EarClipVertex> reflexes = new LinkedList <EarClipVertex>(); LinkedList <EarClipVertex> eartips = new LinkedList <EarClipVertex>(); LinkedListNode <EarClipVertex> n1, n2; EarClipVertex v, v1, v2; EarClipVertex[] t, t1, t2; int countvertices = 0; // Go for all vertices to fill list foreach (EarClipVertex vec in poly) { vec.SetVertsLink(verts.AddLast(vec)); } // Remove any zero-length lines, these will give problems n1 = verts.First; do { // Continue until adjacent zero-length lines are removed n2 = n1.Next ?? verts.First; Vector2D d = n1.Value.Position - n2.Value.Position; while ((Math.Abs(d.x) < 0.00001f) && (Math.Abs(d.y) < 0.00001f)) { n2.Value.Remove(); n2 = n1.Next ?? verts.First; if (n2 != null) { d = n1.Value.Position - n2.Value.Position; } else { break; } } // Next! n1 = n2; }while(n1 != verts.First); // Optimization: Vertices which have lines with the // same angle are useless. Remove them! n1 = verts.First; while (n1 != null) { // Get the next vertex n2 = n1.Next; // Get triangle for v t = GetTriangle(n1.Value); // Check if both lines have the same angle Line2D a = new Line2D(t[0].Position, t[1].Position); Line2D b = new Line2D(t[1].Position, t[2].Position); if (Math.Abs(Angle2D.Difference(a.GetAngle(), b.GetAngle())) < 0.00001f) { // Same angles, remove vertex n1.Value.Remove(); } // Next! n1 = n2; } // Go for all vertices to determine reflex or convex foreach (EarClipVertex vv in verts) { // Add to reflex or convex list if (IsReflex(GetTriangle(vv))) { vv.AddReflex(reflexes); } else { convexes.Add(vv); } } // Go for all convex vertices to see if they are ear tips foreach (EarClipVertex cv in convexes) { // Add when this is a valid ear t = GetTriangle(cv); if (CheckValidEar(t, reflexes)) { cv.AddEarTip(eartips); } } #if DEBUG if (OnShowPolygon != null) { OnShowPolygon(verts); } #endif // Process ears until done while ((eartips.Count > 0) && (verts.Count > 2)) { // Get next ear v = eartips.First.Value; t = GetTriangle(v); // Only save this triangle when it has an area if (TriangleHasArea(t)) { // Add ear as triangle AddTriangleToList(t, verticeslist, sidedefslist, (verts.Count == 3)); countvertices += 3; } // Remove this ear from all lists v.Remove(); v1 = t[0]; v2 = t[2]; #if DEBUG if (TriangleHasArea(t)) { if (OnShowEarClip != null) { OnShowEarClip(t, verts); } } #endif // Test first neighbour t1 = GetTriangle(v1); if (IsReflex(t1)) { // List as reflex if not listed yet if (!v1.IsReflex) { v1.AddReflex(reflexes); } v1.RemoveEarTip(); } else { // Remove from reflexes v1.RemoveReflex(); } // Test second neighbour t2 = GetTriangle(v2); if (IsReflex(t2)) { // List as reflex if not listed yet if (!v2.IsReflex) { v2.AddReflex(reflexes); } v2.RemoveEarTip(); } else { // Remove from reflexes v2.RemoveReflex(); } // Check if any neightbour have become a valid or invalid ear if (!v1.IsReflex && (CheckValidEar(t1, reflexes))) { v1.AddEarTip(eartips); } else { v1.RemoveEarTip(); } if (!v2.IsReflex && (CheckValidEar(t2, reflexes))) { v2.AddEarTip(eartips); } else { v2.RemoveEarTip(); } } #if DEBUG if (OnShowRemaining != null) { OnShowRemaining(verts); } #endif // Dispose remaining vertices foreach (EarClipVertex ecv in verts) { ecv.Dispose(); } // Return the number of vertices in the result return(countvertices); }
// This finds the cut coordinates and splits the other poly with inner vertices private static void SplitOuterWithInner(LinkedListNode <EarClipVertex> start, EarClipPolygon p) { LinkedListNode <EarClipVertex> insertbefore = null; float u, ul, foundu = float.MaxValue; Vector2D foundpos = new Vector2D(); // Create a line from start that goes beyond the right most vertex of p LinkedListNode <EarClipVertex> pr = FindRightMostVertex(p); float startx = start.Value.Position.x; float endx = pr.Value.Position.x + 10.0f; Line2D starttoright = new Line2D(start.Value.Position, new Vector2D(endx, start.Value.Position.y)); // Calculate a small bonus (0.1 mappixel) float bonus = starttoright.GetNearestOnLine(new Vector2D(start.Value.Position.x + 0.1f, start.Value.Position.y)); // Go for all lines in the outer polygon LinkedListNode <EarClipVertex> v1 = p.Last; LinkedListNode <EarClipVertex> v2 = p.First; while (v2 != null) { // Check if the line goes between startx and endx if (((v1.Value.Position.x > startx) || (v2.Value.Position.x > startx)) && ((v1.Value.Position.x < endx) || (v2.Value.Position.x < endx))) { // Find intersection Line2D pl = new Line2D(v1.Value.Position, v2.Value.Position); pl.GetIntersection(starttoright, out u, out ul); if (float.IsNaN(u)) { // We have found a line that is perfectly horizontal // (parallel to the cut scan line) Check if the line // is overlapping the cut scan line. if (v1.Value.Position.y == start.Value.Position.y) { // This is an exceptional situation which causes a bit of a problem, because // this could be a previously made cut, which overlaps another line from the // same cut and we have to determine which of the two we will join with. If we // pick the wrong one, the polygon is no longer valid and triangulation will fail. // Calculate distance of each vertex in units u = starttoright.GetNearestOnLine(v1.Value.Position); ul = starttoright.GetNearestOnLine(v2.Value.Position); // Rule out vertices before the scan line if (u < 0.0f) { u = float.MaxValue; } if (ul < 0.0f) { ul = float.MaxValue; } float insert_u = Math.Min(u, ul); Vector2D inserpos = starttoright.GetCoordinatesAt(insert_u); // Check in which direction the line goes. if (v1.Value.Position.x > v2.Value.Position.x) { // The line goes from right to left (towards our start point) // so we must always insert our cut after this line. // If the next line goes up, we consider this a better candidate than // a horizontal line that goes from left to right (the other cut line) // so we give it a small bonus. LinkedListNode <EarClipVertex> v3 = v2.Next ?? v2.List.First; if (v3.Value.Position.y < v2.Value.Position.y) { insert_u -= bonus; } // Remember this when it is a closer match if (insert_u <= foundu) { insertbefore = v2.Next ?? v2.List.First; foundu = insert_u; foundpos = inserpos; } } else { // The line goes from left to right (away from our start point) // so we must always insert our cut before this line. // If the previous line goes down, we consider this a better candidate than // a horizontal line that goes from right to left (the other cut line) // so we give it a small bonus. LinkedListNode <EarClipVertex> v3 = v1.Previous ?? v1.List.Last; if (v3.Value.Position.y > v1.Value.Position.y) { insert_u -= bonus; } // Remember this when it is a closer match if (insert_u <= foundu) { insertbefore = v2; foundu = insert_u; foundpos = inserpos; } } } } // Found a closer match? else if ((ul >= 0.0f) && (ul <= 1.0f) && (u > 0.0f) && (u <= foundu)) { // Found a closer intersection insertbefore = v2; foundu = u; foundpos = starttoright.GetCoordinatesAt(u); } } // Next v1 = v2; v2 = v2.Next; } // Found anything? if (insertbefore != null) { Sidedef sd = (insertbefore.Previous == null) ? insertbefore.List.Last.Value.Sidedef : insertbefore.Previous.Value.Sidedef; // Find the position where we have to split the outer polygon EarClipVertex split = new EarClipVertex(foundpos, null); // Insert manual split vertices p.AddBefore(insertbefore, new EarClipVertex(split, sd)); // Start inserting from the start (do I make sense this time?) v1 = start; do { // Insert inner polygon vertex p.AddBefore(insertbefore, new EarClipVertex(v1.Value)); v1 = (v1.Next ?? v1.List.First); } while(v1 != start); // Insert manual split vertices p.AddBefore(insertbefore, new EarClipVertex(start.Value, sd)); if (split.Position != insertbefore.Value.Position) { p.AddBefore(insertbefore, new EarClipVertex(split, sd)); } } }
// This traces sector lines to create a polygon tree private static List <EarClipPolygon> DoTrace(Sector s) { Dictionary <Sidedef, bool> todosides = new Dictionary <Sidedef, bool>(s.Sidedefs.Count); Dictionary <Vertex, Vertex> ignores = new Dictionary <Vertex, Vertex>(); List <EarClipPolygon> root = new List <EarClipPolygon>(); // Fill the dictionary // The bool value is used to indicate lines which has been visited in the trace foreach (Sidedef sd in s.Sidedefs) { todosides.Add(sd, false); } // First remove all sides that refer to the same sector on both sides of the line RemoveDoubleSidedefReferences(todosides, s.Sidedefs); // Continue until all sidedefs have been processed while (todosides.Count > 0) { // Reset all visited indicators foreach (Sidedef sd in s.Sidedefs) { if (todosides.ContainsKey(sd)) { todosides[sd] = false; } } // Find the right-most vertex to start a trace with. // This guarantees that we start out with an outer polygon and we just // have to check if it is inside a previously found polygon. Vertex start = FindRightMostVertex(todosides, ignores); // No more possible start vertex found? // Then leave with what we have up till now. if (start == null) { break; } // Trace to find a polygon SidedefsTracePath path = DoTracePath(new SidedefsTracePath(), start, null, s, todosides); // If tracing is not possible (sector not closed?) // then add the start to the ignore list and try again later if (path == null) { // Ignore vertex as start ignores.Add(start, start); } else { // Remove the sides found in the path foreach (Sidedef sd in path) { todosides.Remove(sd); } // Create the polygon EarClipPolygon newpoly = path.MakePolygon(); // Determine where this polygon goes in our tree foreach (EarClipPolygon p in root) { // Insert if it belongs as a child if (p.InsertChild(newpoly)) { // Done newpoly = null; break; } } // Still not inserted in our tree? if (newpoly != null) { // Then add it at root level as outer polygon newpoly.Inner = false; root.Add(newpoly); } } } // Return result return(root); }