added ear clipping (outer polygon only, still have to implement cutting)

This commit is contained in:
codeimp 2008-01-07 19:10:23 +00:00
parent bd6356c999
commit bdf192492f
9 changed files with 434 additions and 8 deletions

View file

@ -91,6 +91,7 @@
<Compile Include="Geometry\Angle2D.cs" />
<Compile Include="Geometry\BSPTriangulator.cs" />
<Compile Include="Geometry\EarClipTriangulator.cs" />
<Compile Include="Geometry\EarClipVertex.cs" />
<Compile Include="Geometry\GLNodesTriangulator.cs" />
<Compile Include="Geometry\Line2D.cs" />
<Compile Include="Geometry\Polygon.cs" />

View file

@ -31,6 +31,7 @@ using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Editing;
using System.Threading;
using System.Drawing;
#endregion
@ -284,7 +285,9 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
EarClipTriangulator t = new EarClipTriangulator();
t.OnShowLine = new EarClipTriangulator.ShowLine(ShowLine);
t.OnShowPolygon = new EarClipTriangulator.ShowPolygon(ShowPolygon);
t.OnShowPoint = new EarClipTriangulator.ShowPoint(ShowPoint);
t.OnShowEarClip = new EarClipTriangulator.ShowEarClip(ShowEarClip);
// Triangulate this now!
triangles = t.Triangulate(General.GetByIndex<Sector>(selected, 0));
@ -305,14 +308,45 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
renderer.RenderLine(triangles[i + 1], triangles[i + 2], General.Colors.Selection);
renderer.RenderLine(triangles[i + 2], triangles[i + 0], General.Colors.Selection);
}
// Done
renderer.Finish();
Thread.Sleep(200);
}
}
}
}
// This shows a point
private void ShowPoint(Vector2D v, int c)
{
for(int a = 0; a < 6; a++)
{
RedrawDisplay();
Thread.Sleep(10);
// Start with a clear display
if(renderer.Start(true, true))
{
// Do not show things
renderer.SetThingsRenderOrder(false);
// Render lines and vertices
renderer.RenderLinedefSet(General.Map.Map.Linedefs);
renderer.RenderVerticesSet(General.Map.Map.Vertices);
// Show the point
renderer.RenderVertexAt(v, c);
// Done
renderer.Finish();
}
// Wait a bit
Thread.Sleep(100);
}
}
// This shows a line
private void ShowLine(Vector2D v1, Vector2D v2, PixelColor c)
{
@ -373,6 +407,85 @@ namespace CodeImp.DoomBuilder.BuilderModes.Editing
Thread.Sleep(100);
}
}
// This shows a polygon
private void ShowEarClip(EarClipVertex[] found, LinkedList<EarClipVertex> remains)
{
EarClipVertex prev, first;
for(int a = 0; a < 5; a++)
{
// Start with a clear display
if(renderer.Start(true, true))
{
// Do not show things
renderer.SetThingsRenderOrder(false);
// Render lines and vertices
renderer.RenderLinedefSet(General.Map.Map.Linedefs);
renderer.RenderVerticesSet(General.Map.Map.Vertices);
// Go for all remaining vertices
prev = null; first = null;
foreach(EarClipVertex v in remains)
{
// Show the line
if(prev != null) renderer.RenderLine(v.Position, prev.Position, PixelColor.FromColor(Color.OrangeRed));
if(prev == null) first = v;
prev = v;
if(v.IsReflex)
renderer.RenderVertexAt(v.Position, ColorCollection.SELECTION);
else
renderer.RenderVertexAt(v.Position, ColorCollection.VERTICES);
}
if(first != null) renderer.RenderLine(first.Position, prev.Position, PixelColor.FromColor(Color.OrangeRed));
if(found != null)
{
renderer.RenderLine(found[0].Position, found[1].Position, PixelColor.FromColor(Color.SkyBlue));
renderer.RenderLine(found[1].Position, found[2].Position, PixelColor.FromColor(Color.SkyBlue));
renderer.RenderLine(found[2].Position, found[0].Position, PixelColor.FromColor(Color.SkyBlue));
renderer.RenderVertexAt(found[1].Position, ColorCollection.ASSOCIATION);
}
// Done
renderer.Finish();
}
Thread.Sleep(10);
// Start with a clear display
if(renderer.Start(true, true))
{
// Do not show things
renderer.SetThingsRenderOrder(false);
// Render lines and vertices
renderer.RenderLinedefSet(General.Map.Map.Linedefs);
renderer.RenderVerticesSet(General.Map.Map.Vertices);
// Go for all remaining vertices
prev = null; first = null;
foreach(EarClipVertex v in remains)
{
// Show the line
if(prev != null) renderer.RenderLine(v.Position, prev.Position, PixelColor.FromColor(Color.OrangeRed));
if(prev == null) first = v;
prev = v;
if(v.IsReflex)
renderer.RenderVertexAt(v.Position, ColorCollection.SELECTION);
else
renderer.RenderVertexAt(v.Position, ColorCollection.VERTICES);
}
if(first != null) renderer.RenderLine(first.Position, prev.Position, PixelColor.FromColor(Color.OrangeRed));
// Done
renderer.Finish();
}
Thread.Sleep(20);
}
}
#endregion
}

View file

@ -35,6 +35,7 @@ namespace CodeImp.DoomBuilder.Geometry
/// Responsible for creating and caching sector polygons.
/// Performs triangulation of sectors by using ear clipping.
/// </summary>
/// See: http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
public sealed class EarClipTriangulator : Triangulator
{
#region ================== Delegates
@ -43,11 +44,15 @@ namespace CodeImp.DoomBuilder.Geometry
// 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);
public delegate void ShowPoint(Vector2D v, int c);
public delegate void ShowEarClip(EarClipVertex[] found, LinkedList<EarClipVertex> remaining);
// For debugging purpose only!
// These are not called in a release build
public ShowLine OnShowLine;
public ShowPolygon OnShowPolygon;
public ShowPoint OnShowPoint;
public ShowEarClip OnShowEarClip;
#endregion
@ -94,6 +99,7 @@ namespace CodeImp.DoomBuilder.Geometry
// This triangulates a sector and stores it
protected override void PerformTriangulation(Sector sector)
{
TriangleList triangles = new TriangleList();
List<Polygon> polys;
/*
@ -112,10 +118,14 @@ namespace CodeImp.DoomBuilder.Geometry
// TRACING
polys = DoTrace(sector);
// TODO: CUTTING
// EAR-CLIPPING
foreach(Polygon p in polys) triangles.AddRange(DoEarClip(p));
// DEBUG:
base.StoreTriangles(sector, new TriangleList());
// STORE
base.StoreTriangles(sector, triangles);
}
#endregion
@ -148,7 +158,7 @@ namespace CodeImp.DoomBuilder.Geometry
// 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);
@ -309,6 +319,150 @@ namespace CodeImp.DoomBuilder.Geometry
#region ================== Ear Clipping
// This clips a polygon and returns the triangles
// The polygon may not have any holes or islands
private TriangleList DoEarClip(Polygon poly)
{
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>();
TriangleList result = new TriangleList();
EarClipVertex v, v1, v2;
EarClipVertex[] t, t1, t2;
// Go for all vertices to fill list
foreach(Vector2D vec in poly)
{
// Add to main list
v = new EarClipVertex(vec);
v.SetVertsLink(verts.AddLast(v));
}
// 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 a valid ear
t = GetTriangle(cv);
if(CheckValidEar(t, reflexes)) cv.AddEarTip(eartips);
}
// Process ears until done
while((eartips.Count > 0) && (verts.Count > 2))
{
// Get next ear
v = eartips.First.Value;
t = GetTriangle(v);
// Add ear as triangle
result.Add(t);
// Remove this ear from all lists
v.Remove();
v1 = t[0];
v2 = t[2];
#if DEBUG
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();
}
// Return result
return result;
}
// 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)
{
// Return false on intersection
if(PointInsideTriangle(t, rv.Position) &&
(rv != t[0]) && (rv != t[1]) && (rv != t[2])) 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];
if(v.MainListNode.Previous == null) t[0] = v.MainListNode.List.Last.Value; else t[0] = v.MainListNode.Previous.Value;
t[1] = v;
if(v.MainListNode.Next == null) t[2] = v.MainListNode.List.First.Value; else t[2] = 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.00001f);
}
// This checks if a point is inside a triangle
// NOTE: vertices in t must be in clockwise order!
private bool PointInsideTriangle(EarClipVertex[] t, Vector2D p)
{
#if DEBUG
float a = Line2D.GetSideOfLine(t[0].Position, t[1].Position, p);
float b = Line2D.GetSideOfLine(t[1].Position, t[2].Position, p);
float c = Line2D.GetSideOfLine(t[2].Position, t[0].Position, p);
return (a < 0.00001f) && (b < 0.00001f) && (c < 0.00001f);
#else
return (Line2D.GetSideOfLine(t[0].Position, t[1].Position, p) < 0.00001f) &&
(Line2D.GetSideOfLine(t[1].Position, t[2].Position, p) < 0.00001f) &&
(Line2D.GetSideOfLine(t[2].Position, t[0].Position, p) < 0.00001f);
#endif
}
#endregion
}
}

View file

@ -0,0 +1,128 @@
#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
{
public class EarClipVertex
{
#region ================== Variables
// Position
private Vector2D pos;
// Lists
private LinkedListNode<EarClipVertex> vertslink;
private LinkedListNode<EarClipVertex> reflexlink;
private LinkedListNode<EarClipVertex> eartiplink;
#endregion
#region ================== Properties
public Vector2D Position { get { return pos; } }
internal LinkedListNode<EarClipVertex> MainListNode { get { return vertslink; } }
public bool IsReflex { get { return (reflexlink != null); } }
public bool IsEarTip { get { return (eartiplink != null); } }
#endregion
#region ================== Constructor / Disposer
// Constructor
internal EarClipVertex(Vector2D v)
{
// Initialize
this.pos = v;
// We have no destructor
GC.SuppressFinalize(this);
}
// Disposer
internal void Dispose()
{
reflexlink = null;
eartiplink = null;
vertslink = null;
}
#endregion
#region ================== Methods
// This sets the main linked list node
internal void SetVertsLink(LinkedListNode<EarClipVertex> link)
{
this.vertslink = link;
}
// This removes the item from all lists
internal void Remove()
{
vertslink.List.Remove(vertslink);
if(reflexlink != null) reflexlink.List.Remove(reflexlink);
if(eartiplink != null) eartiplink.List.Remove(eartiplink);
reflexlink = null;
eartiplink = null;
vertslink = null;
}
// This adds to reflexes list
public void AddReflex(LinkedList<EarClipVertex> reflexes)
{
if(vertslink == null) throw new Exception();
if(reflexlink == null) reflexlink = reflexes.AddLast(this);
}
// This removes from reflexes list
internal void RemoveReflex()
{
if(reflexlink != null) reflexlink.List.Remove(reflexlink);
reflexlink = null;
}
// This adds to eartips list
internal void AddEarTip(LinkedList<EarClipVertex> eartips)
{
if(vertslink == null) throw new Exception();
if(eartiplink == null) eartiplink = eartips.AddLast(this);
}
// This removes from eartips list
internal void RemoveEarTip()
{
if(eartiplink != null) eartiplink.List.Remove(eartiplink);
eartiplink = null;
}
#endregion
}
}

View file

@ -33,5 +33,12 @@ namespace CodeImp.DoomBuilder.Geometry
{
public class TriangleList : List<Vector2D>
{
// This adds a triangle
internal void Add(EarClipVertex[] t)
{
base.Add(t[0].Position);
base.Add(t[1].Position);
base.Add(t[2].Position);
}
}
}

View file

@ -153,6 +153,17 @@ namespace CodeImp.DoomBuilder.Geometry
return a.x * b.x + a.y * b.y;
}
// This calculates the cross product
public static Vector2D CrossProduct(Vector2D a, Vector2D b)
{
Vector2D result = new Vector2D();
// Calculate and return the dot product
result.x = a.y * b.x;
result.y = a.x * b.y;
return result;
}
// This compares a vector
public static bool operator ==(Vector2D a, Vector2D b)
{

View file

@ -65,6 +65,7 @@ namespace CodeImp.DoomBuilder.Rendering
void RenderThing(Thing t, PixelColor c);
void RenderThingSet(ICollection<Thing> things);
void RenderVertex(Vertex v, int colorindex);
void RenderVertexAt(Vector2D v, int colorindex);
void RenderVerticesSet(ICollection<Vertex> vertices);
}
}

View file

@ -84,6 +84,7 @@ namespace CodeImp.DoomBuilder.Rendering
// Terminate
VirtualFree((void*)memory, new UIntPtr(memorysize), General.MEM_RELEASE);
GC.RemoveMemoryPressure(memorysize);
memorysize = 0;
}
#endregion
@ -93,7 +94,7 @@ namespace CodeImp.DoomBuilder.Rendering
// This clears the memory black
public void Clear()
{
General.ZeroMemory(new IntPtr(memory), (int)memorysize);
if(memorysize > 0) General.ZeroMemory(new IntPtr(memory), (int)memorysize);
}
#endregion

View file

@ -966,6 +966,16 @@ namespace CodeImp.DoomBuilder.Rendering
// Draw pixel here
plotter.DrawVertexSolid((int)nv.x, (int)nv.y, vertexsize, General.Colors.Colors[colorindex], General.Colors.BrightColors[colorindex], General.Colors.DarkColors[colorindex]);
}
// This renders a single vertex at specified coordinates
public void RenderVertexAt(Vector2D v, int colorindex)
{
// Transform vertex coordinates
Vector2D nv = v.GetTransformed(translatex, translatey, scale, -scale);
// Draw pixel here
plotter.DrawVertexSolid((int)nv.x, (int)nv.y, vertexsize, General.Colors.Colors[colorindex], General.Colors.BrightColors[colorindex], General.Colors.DarkColors[colorindex]);
}
// This renders a set of vertices
public void RenderVerticesSet(ICollection<Vertex> vertices)