mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-23 04:12:12 +00:00
added ear clipping (outer polygon only, still have to implement cutting)
This commit is contained in:
parent
bd6356c999
commit
bdf192492f
9 changed files with 434 additions and 8 deletions
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
128
Source/Geometry/EarClipVertex.cs
Normal file
128
Source/Geometry/EarClipVertex.cs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue