UltimateZoneBuilder/Source/Core/Geometry/Triangulation.cs
MaxED 0369c969d1 According to dotnetperls.com, "new Dictionary<string, [anything]>(StringComparer.Ordinal)" works 17% faster than "new Dictionary<string, [anything]>()", so let's stick that everywhere and see what happens :)
Draw Curve Mode: added settings panel.
Sectors mode: added "Make Door" button to the toolbar.
Swapped Side panel and Info panel z-order. 
Interface: split toolbar into 3 separate toolbars. All toolbar buttons are now viewable at 1024x768.
Interface: grouped stuff in "Modes" menu a bit better.
Interface: added "Draw [stuff]" buttons to modes toolbar.
Interface: reorganized main menu. Hope it makes more sense now.
API: added General.Interface.AddModesButton() and General.Interface.AddModesMenu(), which can be used to add buttons to specific group in "Modes" toolbar and menu items to specific group in "Modes" menu, so actions, which behave like an editing mode, but are not part of one can be added there.
2014-02-26 14:11:06 +00:00

969 lines
31 KiB
C#

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections.Generic;
using CodeImp.DoomBuilder.Map;
using System.Collections.ObjectModel;
using CodeImp.DoomBuilder.IO;
#endregion
namespace CodeImp.DoomBuilder.Geometry
{
/// <summary>
/// Responsible for creating sector polygons.
/// Performs triangulation of sectors by using ear clipping.
/// </summary>
public sealed class Triangulation
{
#region ================== Delegates
#if DEBUG
// For debugging purpose only!
// These are not called in a release build
public delegate void ShowPolygon(LinkedList<EarClipVertex> p);
public delegate void ShowEarClip(EarClipVertex[] found, LinkedList<EarClipVertex> remaining);
public delegate void ShowRemaining(LinkedList<EarClipVertex> remaining);
// For debugging purpose only!
// These are not called in a release build
public ShowPolygon OnShowPolygon;
public ShowEarClip OnShowEarClip;
public ShowRemaining OnShowRemaining;
#endif
#endregion
#region ================== Constants
#endregion
#region ================== Variables
// Number of vertices per island
private ReadOnlyCollection<int> islandvertices;
// Vertices that result from the triangulation, 3 per triangle.
private ReadOnlyCollection<Vector2D> vertices;
// These sidedefs match with the vertices. If a vertex is not the start
// along a sidedef, this list contains a null entry for that vertex.
private ReadOnlyCollection<Sidedef> sidedefs;
// Temporary array for the sidedefs deserialization
private int[] sidedefindices;
#endregion
#region ================== Properties
public ReadOnlyCollection<int> IslandVertices { get { return islandvertices; } }
public ReadOnlyCollection<Vector2D> Vertices { get { return vertices; } }
public ReadOnlyCollection<Sidedef> Sidedefs { get { return sidedefs; } }
#endregion
#region ================== Constructor / Disposer
// Constructor
public static Triangulation Create(Sector sector)
{
Triangulation t = new Triangulation();
t.Triangulate(sector);
return t;
}
// Constructor
public Triangulation()
{
islandvertices = Array.AsReadOnly<int>(new int[0]);
vertices = Array.AsReadOnly<Vector2D>(new Vector2D[0]);
sidedefs = Array.AsReadOnly<Sidedef>(new Sidedef[0]);
}
// This performs the triangulation
public void Triangulate(Sector s)
{
// Initialize
List<EarClipPolygon> polys;
List<int> islandslist = new List<int>();
List<Vector2D> verticeslist = new List<Vector2D>();
List<Sidedef> sidedefslist = new List<Sidedef>();
// We have no destructor
GC.SuppressFinalize(this);
/*
* This process is divided into several steps:
*
* 1) Tracing the sector lines to find clockwise outer polygons
* and counter-clockwise inner polygons. These are arranged in a
* polygon tree for the next step.
*
* 2) Cutting the inner polygons to make a flat list of only
* outer polygons.
*
* 3) Ear-clipping the polygons to create triangles.
*
*/
// TRACING
polys = DoTrace(s);
// CUTTING
DoCutting(polys);
// EAR-CLIPPING
foreach(EarClipPolygon p in polys)
islandslist.Add(DoEarClip(p, verticeslist, sidedefslist));
// Make arrays
islandvertices = Array.AsReadOnly<int>(islandslist.ToArray());
vertices = Array.AsReadOnly<Vector2D>(verticeslist.ToArray());
sidedefs = Array.AsReadOnly<Sidedef>(sidedefslist.ToArray());
}
#endregion
#region ================== Serialization
// Serialize / deserialize
internal void ReadWrite(IReadWriteStream s)
{
if(s.IsWriting)
{
s.wInt(islandvertices.Count);
for(int i = 0; i < islandvertices.Count; i++) s.wInt(islandvertices[i]);
s.wInt(vertices.Count);
for(int i = 0; i < vertices.Count; i++) s.wVector2D(vertices[i]);
s.wInt(sidedefs.Count);
for(int i = 0; i < sidedefs.Count; i++)
{
if(sidedefs[i] != null)
s.wInt(sidedefs[i].SerializedIndex);
else
s.wInt(-1);
}
}
else
{
int c;
s.rInt(out c);
int[] islandverticeslist = new int[c];
for(int i = 0; i < c; i++) s.rInt(out islandverticeslist[i]);
islandvertices = Array.AsReadOnly<int>(islandverticeslist);
s.rInt(out c);
Vector2D[] verticeslist = new Vector2D[c];
for(int i = 0; i < c; i++) s.rVector2D(out verticeslist[i]);
vertices = Array.AsReadOnly<Vector2D>(verticeslist);
s.rInt(out c);
sidedefindices = new int[c];
for(int i = 0; i < c; i++) s.rInt(out sidedefindices[i]);
}
}
// After deserialization we need to find the actual sidedefs back
internal void PostDeserialize(MapSet map)
{
// Find our sidedefs
List<Sidedef> sides = new List<Sidedef>(sidedefindices.Length);
for(int i = 0; i < sidedefindices.Length; i++)
{
if(sidedefindices[i] >= 0)
sides.Add(map.SidedefIndices[sidedefindices[i]]);
else
sides.Add(null);
}
// We don't need this array any longer
sidedefindices = null;
// Keep readonly array
sidedefs = Array.AsReadOnly<Sidedef>(sides.ToArray());
}
#endregion
#region ================== Tracing
// This traces sector lines to create a polygon tree
private 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>();
SidedefsTracePath path;
EarClipPolygon newpoly;
Vertex start;
// 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.
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
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
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;
}
// This recursively traces a path
// Returns the resulting TracePath when the search is complete
// or returns null when no path found.
private SidedefsTracePath DoTracePath(SidedefsTracePath history, Vertex fromhere, Vertex findme, Sector sector, Dictionary<Sidedef, bool> sides)
{
SidedefsTracePath nextpath;
SidedefsTracePath result;
Vertex nextvertex;
List<Sidedef> allsides;
// Found the vertex we are tracing to?
if(fromhere == findme) return history;
// On the first run, findme is null (otherwise the trace would end
// immeditely when it starts) so set findme here on the first run.
if(findme == null) findme = fromhere;
// Make a list of sides referring to the same sector
allsides = new List<Sidedef>(fromhere.Linedefs.Count * 2);
foreach(Linedef l in fromhere.Linedefs)
{
// Should we go along the front or back side?
// This is very important for clockwise polygon orientation!
if(l.Start == fromhere)
{
// Front side of line connected to sector?
if((l.Front != null) && (l.Front.Sector == sector))
{
// Visit here when not visited yet
if(sides.ContainsKey(l.Front) && !sides[l.Front]) allsides.Add(l.Front);
}
}
else
{
// Back side of line connected to sector?
if((l.Back != null) && (l.Back.Sector == sector))
{
// Visit here when not visited yet
if(sides.ContainsKey(l.Back) && !sides[l.Back]) allsides.Add(l.Back);
}
}
}
// Previous line available?
if(history.Count > 0)
{
// This is done to ensure the tracing works along vertices that are shared by
// more than 2 lines/sides of the same sector. We must continue tracing along
// the first next smallest delta angle! This sorts the smallest delta angle to
// the top of the list.
SidedefAngleSorter sorter = new SidedefAngleSorter(history[history.Count - 1], fromhere);
allsides.Sort(sorter);
}
// Go for all lines connected to this vertex
foreach(Sidedef s in allsides)
{
// Mark sidedef as visited and move to next vertex
sides[s] = true;
nextpath = new SidedefsTracePath(history, s);
if(s.Line.Start == fromhere) nextvertex = s.Line.End; else nextvertex = s.Line.Start;
result = DoTracePath(nextpath, nextvertex, findme, sector, sides);
if(result != null) return result;
}
// Nothing found
return null;
}
// This removes all sidedefs which has a sidedefs on the other side
// of the same line that refers to the same sector. These are removed
// because they are useless and make the triangulation inefficient.
private void RemoveDoubleSidedefReferences(Dictionary<Sidedef, bool> todosides, ICollection<Sidedef> sides)
{
// Go for all sides
foreach(Sidedef sd in sides)
{
// Double sided?
if(sd.Other != null)
{
// Referring to the same sector on both sides?
if(sd.Sector == sd.Other.Sector)
{
// Remove this one
todosides.Remove(sd);
}
}
}
}
// This finds the right-most vertex to start tracing with
private Vertex FindRightMostVertex(Dictionary<Sidedef, bool> sides, Dictionary<Vertex, Vertex> ignores)
{
Vertex found = null;
// Go for all sides to find the right-most side
foreach(KeyValuePair<Sidedef, bool> sd in sides)
{
// First found?
if((found == null) && !ignores.ContainsKey(sd.Key.Line.Start)) found = sd.Key.Line.Start;
if((found == null) && !ignores.ContainsKey(sd.Key.Line.End)) found = sd.Key.Line.End;
// Compare?
if(found != null)
{
// Check if more to the right than the previous found
if((sd.Key.Line.Start.Position.x > found.Position.x) && !ignores.ContainsKey(sd.Key.Line.Start)) found = sd.Key.Line.Start;
if((sd.Key.Line.End.Position.x > found.Position.x) && !ignores.ContainsKey(sd.Key.Line.End)) found = sd.Key.Line.End;
}
}
// Return result
return found;
}
#endregion
#region ================== Cutting
// This cuts into outer polygons to solve inner polygons and make the polygon tree flat
private 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 takes an outer polygon and a set of inner polygons to start cutting on
private 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 todo 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 finds the right-most vertex in an inner polygon to use for cut startpoint.
private 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 finds the cut coordinates and splits the other poly with inner vertices
private void SplitOuterWithInner(LinkedListNode<EarClipVertex> start, EarClipPolygon p)
{
LinkedListNode<EarClipVertex> v1, v2;
LinkedListNode<EarClipVertex> insertbefore = null;
float u, ul, bonus, foundu = float.MaxValue;
Vector2D foundpos = new Vector2D();
EarClipVertex split;
// 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)
bonus = starttoright.GetNearestOnLine(new Vector2D(start.Value.Position.x + 0.1f, start.Value.Position.y));
// Go for all lines in the outer polygon
v1 = p.Last;
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
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));
if(v1.Next != null) v1 = v1.Next; else v1 = 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));
}
}
#endregion
#region ================== Ear Clipping
// 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);
bool t1a = true; //TriangleHasArea(t1);
if(t1a && 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);
bool t2a = true; //TriangleHasArea(t2);
if(t2a && 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 && (!t1a || CheckValidEar(t1, reflexes))) v1.AddEarTip(eartips); else v1.RemoveEarTip();
if(!v2.IsReflex && (!t2a || 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 checks if a given ear is a valid (no intersections from reflex vertices)
private bool CheckValidEar(EarClipVertex[] t, LinkedList<EarClipVertex> reflexes)
{
// Go for all reflex vertices
foreach(EarClipVertex rv in reflexes)
{
// Not one of the triangle corners?
if((rv.Position != t[0].Position) && (rv.Position != t[1].Position) && (rv.Position != t[2].Position))
{
// Return false on intersection
if(PointInsideTriangle(t, rv.MainListNode)) return false;
}
}
// Valid ear!
return true;
}
// This returns the 3-vertex array triangle for an ear
private EarClipVertex[] GetTriangle(EarClipVertex v)
{
EarClipVertex[] t = new EarClipVertex[3];
t[0] = (v.MainListNode.Previous == null) ? v.MainListNode.List.Last.Value : v.MainListNode.Previous.Value;
t[1] = v;
t[2] = (v.MainListNode.Next == null) ? v.MainListNode.List.First.Value : v.MainListNode.Next.Value;
return t;
}
// This checks if a vertex is reflex (corner > 180 deg) or convex (corner < 180 deg)
private bool IsReflex(EarClipVertex[] t)
{
// Return true when corner is > 180 deg
return (Line2D.GetSideOfLine(t[0].Position, t[2].Position, t[1].Position) < 0.0f);
}
// This checks if a point is inside a triangle
// When the point is on an edge of the triangle, it depends on the lines
// adjacent to the point if it is considered inside or not
// NOTE: vertices in t must be in clockwise order!
private bool PointInsideTriangle(EarClipVertex[] t, LinkedListNode<EarClipVertex> p)
{
// If the triangle has no area, there can never be a point inside
if(TriangleHasArea(t))
{
float lineside01 = Line2D.GetSideOfLine(t[0].Position, t[1].Position, p.Value.Position);
float lineside12 = Line2D.GetSideOfLine(t[1].Position, t[2].Position, p.Value.Position);
float lineside20 = Line2D.GetSideOfLine(t[2].Position, t[0].Position, p.Value.Position);
float u_on_line = 0.5f;
// If point p is on the line of an edge, find out where on the edge segment p is.
if(lineside01 == 0.0f)
u_on_line = Line2D.GetNearestOnLine(t[0].Position, t[1].Position, p.Value.Position);
else if(lineside12 == 0.0f)
u_on_line = Line2D.GetNearestOnLine(t[1].Position, t[2].Position, p.Value.Position);
else if(lineside20 == 0.0f)
u_on_line = Line2D.GetNearestOnLine(t[2].Position, t[0].Position, p.Value.Position);
// If any of the lineside results are 0 then that means the point p lies on that edge and we
// need to test if the lines adjacent to the point p are in the triangle or not.
// If the lines are intersecting the triangle, we also consider the point inside.
if((lineside01 == 0.0f) || (lineside12 == 0.0f) || (lineside20 == 0.0f))
{
// When the point p is outside the edge segment, then it is not inside the triangle
if((u_on_line < 0.0f) || (u_on_line > 1.0f))
return false;
// Point p is on an edge segment. We'll have to decide by it's lines if we call it inside or outside the triangle.
LinkedListNode<EarClipVertex> p1 = p.Previous ?? p.List.Last;
LinkedListNode<EarClipVertex> p2 = p.Next ?? p.List.First;
if(LineInsideTriangle(t, p.Value.Position, p1.Value.Position)) return true;
if(LineInsideTriangle(t, p.Value.Position, p2.Value.Position)) return true;
return false;
}
else
{
return (lineside01 < 0.0f) && (lineside12 < 0.0f) && (lineside20 < 0.0f);
}
}
else
{
return false;
}
}
// This checks if a line is inside a triangle (touching the triangle is allowed)
// NOTE: We already know p1 is on an edge segment of the triangle
private bool LineInsideTriangle(EarClipVertex[] t, Vector2D p1, Vector2D p2)
{
float s01 = Line2D.GetSideOfLine(t[0].Position, t[1].Position, p2);
float s12 = Line2D.GetSideOfLine(t[1].Position, t[2].Position, p2);
float s20 = Line2D.GetSideOfLine(t[2].Position, t[0].Position, p2);
float p2_on_edge = 2.0f; // somewhere outside the 0 .. 1 range
float p1_on_same_edge = 2.0f;
// Test if p2 is inside the triangle
if((s01 < 0.0f) && (s12 < 0.0f) && (s20 < 0.0f))
{
// Line is inside triangle, because p2 is
return true;
}
// Test if p2 is on an edge of the triangle and if it is we would
// like to know where on the edge segment p2 is
else if(s01 == 0.0f)
{
p2_on_edge = Line2D.GetNearestOnLine(t[0].Position, t[1].Position, p2);
p1_on_same_edge = Line2D.GetSideOfLine(t[0].Position, t[1].Position, p1);
}
else if(s12 == 0.0f)
{
p2_on_edge = Line2D.GetNearestOnLine(t[1].Position, t[2].Position, p2);
p1_on_same_edge = Line2D.GetSideOfLine(t[1].Position, t[2].Position, p1);
}
else if(s20 == 0.0f)
{
p2_on_edge = Line2D.GetNearestOnLine(t[2].Position, t[0].Position, p2);
p1_on_same_edge = Line2D.GetSideOfLine(t[2].Position, t[0].Position, p1);
}
// Is p2 actually on the edge segment?
if((p2_on_edge >= 0.0f) && (p2_on_edge <= 1.0f))
{
// If p1 is on the same edge (or the unlimited line of that edge)
// then the line is not inside this triangle.
if(p1_on_same_edge == 0.0f)
return false;
}
// Do a complete line-triangle intersection test
// We already know p1 is not inside the triangle (possibly on an edge)
Line2D p = new Line2D(p1, p2);
Line2D t01 = new Line2D(t[0].Position, t[1].Position);
Line2D t12 = new Line2D(t[1].Position, t[2].Position);
Line2D t20 = new Line2D(t[2].Position, t[0].Position);
float pu, pt;
// Test intersections
t01.GetIntersection(p, out pu, out pt);
if(!float.IsNaN(pu) && (pu >= 0.0f) && (pu <= 1.0f) && (pt >= 0.0f) && (pt <= 1.0f)) return true;
t12.GetIntersection(p, out pu, out pt);
if(!float.IsNaN(pu) && (pu >= 0.0f) && (pu <= 1.0f) && (pt >= 0.0f) && (pt <= 1.0f)) return true;
t20.GetIntersection(p, out pu, out pt);
if(!float.IsNaN(pu) && (pu >= 0.0f) && (pu <= 1.0f) && (pt >= 0.0f) && (pt <= 1.0f)) return true;
return false;
}
// This checks if the triangle has an area greater than 0
private bool TriangleHasArea(EarClipVertex[] t)
{
return ((t[0].Position.x * (t[1].Position.y - t[2].Position.y) +
t[1].Position.x * (t[2].Position.y - t[0].Position.y) +
t[2].Position.x * (t[0].Position.y - t[1].Position.y)) != 0.0f);
}
// This adds an array of vertices
private void AddTriangleToList(EarClipVertex[] triangle, List<Vector2D> verticeslist, List<Sidedef> sidedefslist, bool last)
{
// Create triangle
verticeslist.Add(triangle[0].Position);
sidedefslist.Add(triangle[0].Sidedef);
verticeslist.Add(triangle[1].Position);
sidedefslist.Add(triangle[1].Sidedef);
verticeslist.Add(triangle[2].Position);
if(!last) sidedefslist.Add(null); else sidedefslist.Add(triangle[2].Sidedef);
// Modify the first earclipvertex of this triangle, it no longer lies along a sidedef
triangle[0].Sidedef = null;
}
#endregion
}
}