UltimateZoneBuilder/Source/Geometry/EarClipTriangulator.cs

314 lines
8.5 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;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Rendering;
using SlimDX.Direct3D;
using System.Drawing;
using CodeImp.DoomBuilder.Map;
#endregion
namespace CodeImp.DoomBuilder.Geometry
{
/// <summary>
/// Responsible for creating and caching sector polygons.
/// Performs triangulation of sectors by using ear clipping.
/// </summary>
public sealed class EarClipTriangulator : Triangulator
{
#region ================== Delegates
// For debugging purpose only!
// These are not called in a release build
public delegate void ShowLine(Vector2D v1, Vector2D v2, PixelColor c);
public delegate void ShowPolygon(Polygon p, PixelColor c);
// For debugging purpose only!
// These are not called in a release build
public ShowLine OnShowLine;
public ShowPolygon OnShowPolygon;
#endregion
#region ================== Constants
#endregion
#region ================== Variables
#endregion
#region ================== Properties
#endregion
#region ================== Constructor / Disposer
// Constructor
public EarClipTriangulator()
{
// Initialize
// We have no destructor
GC.SuppressFinalize(this);
}
// Diposer
public override void Dispose()
{
// Not already disposed?
if(!isdisposed)
{
// Clean up
// Done
base.Dispose();
}
}
#endregion
#region ================== Methods
// This triangulates a sector and stores it
protected override void PerformTriangulation(Sector sector)
{
List<Polygon> polys;
/*
* 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(sector);
// DEBUG:
base.StoreTriangles(sector, new TriangleList());
}
#endregion
#region ================== Tracing
// This traces sector lines to create a polygon tree
private List<Polygon> DoTrace(Sector s)
{
Dictionary<Sidedef, bool> todosides = new Dictionary<Sidedef, bool>(s.Sidedefs.Count);
List<Polygon> root = new List<Polygon>();
TracePath path;
Polygon 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);
// Trace to find a polygon
path = DoTracePath(new TracePath(), start, null, s, todosides);
// If tracing is not possible (sector not closed?)
// then leave with what we have up till now
if(path == null) break;
// 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(Polygon p in root)
{
// Insert if it belongs as a child
if(p.InsertChild(newpoly))
{
#if DEBUG
if(newpoly.Inner)
{
if(OnShowPolygon != null) OnShowPolygon(newpoly, PixelColor.FromColor(Color.DodgerBlue));
}
else
{
if(OnShowPolygon != null) OnShowPolygon(newpoly, PixelColor.FromColor(Color.OrangeRed));
}
#endif
// 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);
#if DEBUG
if(OnShowPolygon != null) OnShowPolygon(newpoly, General.Colors.Selection);
#endif
}
}
// 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 TracePath DoTracePath(TracePath history, Vertex fromhere, Vertex findme, Sector sector, Dictionary<Sidedef, bool> sides)
{
TracePath nextpath;
TracePath result;
Vertex nextvertex;
// 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;
// Go for all lines connected to this vertex
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])
{
// Mark sidedef as visited and move to next vertex
sides[l.Front] = true;
nextpath = new TracePath(history, l.Front);
if(l.Start == fromhere) nextvertex = l.End; else nextvertex = l.Start;
result = DoTracePath(nextpath, nextvertex, findme, sector, sides);
if(result != null) return result;
}
}
}
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])
{
// Mark sidedef as visited and move to next vertex
sides[l.Back] = true;
nextpath = new TracePath(history, l.Back);
if(l.Start == fromhere) nextvertex = l.End; else nextvertex = l.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)
{
Vertex found = General.GetByIndex<Sidedef>(sides.Keys, 0).Line.Start;
// Go for all sides to find the right-most side
foreach(KeyValuePair<Sidedef, bool> sd in sides)
{
// Check if more to the right than the last found
if(sd.Key.Line.Start.X > found.X) found = sd.Key.Line.Start;
if(sd.Key.Line.End.X > found.X) found = sd.Key.Line.End;
}
// Return result
return found;
}
#endregion
#region ================== Cutting
#endregion
#region ================== Ear Clipping
#endregion
}
}