diff --git a/LibTessDotNet/Sources/Dict.cs b/LibTessDotNet/Sources/Dict.cs index 95fbbfe..6092b69 100644 --- a/LibTessDotNet/Sources/Dict.cs +++ b/LibTessDotNet/Sources/Dict.cs @@ -39,7 +39,7 @@ namespace LibTessDotNet { internal class Dict where TValue : class { - public class Node + public class Node : Pooled { internal TValue _key; internal Node _prev, _next; @@ -47,22 +47,50 @@ public class Node public TValue Key { get { return _key; } } public Node Prev { get { return _prev; } } public Node Next { get { return _next; } } + + public void Init(IPool pool) + { + _key = null; + _prev = null; + _next = null; + } + + public void Reset(IPool pool) + { + _key = null; + _prev = null; + _next = null; + } } public delegate bool LessOrEqual(TValue lhs, TValue rhs); + private IPool _pool; private LessOrEqual _leq; Node _head; - public Dict(LessOrEqual leq) + public bool Empty { get { return _head._next == _head; } } + + public Dict(IPool pool, LessOrEqual leq) { + _pool = pool; _leq = leq; - _head = new Node { _key = null }; + Init(); + } + + public void Init() + { + _head = _pool.Get(); _head._prev = _head; _head._next = _head; } + public void Reset() + { + _pool.Return(ref _head); + } + public Node Insert(TValue key) { return InsertBefore(_head, key); @@ -74,7 +102,8 @@ public Node InsertBefore(Node node, TValue key) node = node._prev; } while (node._key != null && !_leq(node._key, key)); - var newNode = new Node { _key = key }; + var newNode = _pool.Get(); + newNode._key = key; newNode._next = node._next; node._next._prev = newNode; newNode._prev = node; @@ -101,6 +130,7 @@ public void Remove(Node node) { node._next._prev = node._prev; node._prev._next = node._next; + _pool.Return(ref node); } } } diff --git a/LibTessDotNet/Sources/Mesh.cs b/LibTessDotNet/Sources/Mesh.cs index 3b85e00..cbf2d85 100644 --- a/LibTessDotNet/Sources/Mesh.cs +++ b/LibTessDotNet/Sources/Mesh.cs @@ -87,18 +87,18 @@ public void Reset(IPool pool) for (MeshUtils.Face f = _fHead, fNext = _fHead; f._next != null; f = fNext) { fNext = f._next; - pool.Return(f); + pool.Return(ref f); } for (MeshUtils.Vertex v = _vHead, vNext = _vHead; v._next != null; v = vNext) { vNext = v._next; - pool.Return(v); + pool.Return(ref v); } for (MeshUtils.Edge e = _eHead, eNext = _eHead; e._next != null; e = eNext) { eNext = e._next; - pool.Return(e._Sym); - pool.Return(e); + pool.Return(ref e._Sym); + pool.Return(ref e); } _vHead = null; @@ -389,7 +389,7 @@ public void ZapFace(IPool pool, MeshUtils.Face fZap) fNext._prev = fPrev; fPrev._next = fNext; - pool.Return(fZap); + pool.Return(ref fZap); } public void MergeConvexFaces(IPool pool, int maxVertsPerFace) diff --git a/LibTessDotNet/Sources/MeshUtils.cs b/LibTessDotNet/Sources/MeshUtils.cs index cbe3270..3a8b219 100644 --- a/LibTessDotNet/Sources/MeshUtils.cs +++ b/LibTessDotNet/Sources/MeshUtils.cs @@ -127,9 +127,7 @@ public interface ITypePool { private Queue _pool = new Queue(); - private static readonly Func OptimizedInstantiator = Expression.Lambda>( - Expression.New(typeof(T)) - ).Compile(); + private static readonly Func Creator = Expression.Lambda>(Expression.New(typeof(T))).Compile(); public object Get() { @@ -140,7 +138,7 @@ public object Get() return _pool.Dequeue(); } } - return OptimizedInstantiator.Invoke(); + return Creator(); } public void Return(object obj) @@ -165,14 +163,16 @@ public abstract class IPool { public IPool() { + Register(new DefaultTypePool()); Register(new DefaultTypePool()); Register(new DefaultTypePool()); Register(new DefaultTypePool()); Register(new DefaultTypePool()); + Register.Node>(new DefaultTypePool.Node>()); } public abstract void Register(ITypePool typePool) where T : class, Pooled, new(); public abstract T Get() where T : class, Pooled, new(); - public abstract void Return(T obj) where T : class, Pooled, new(); + public abstract void Return(ref T obj) where T : class, Pooled, new(); } public class NullPool : IPool @@ -188,8 +188,9 @@ public override void Register(ITypePool typePool) { } - public override void Return(T obj) + public override void Return(ref T obj) { + obj = null; } } @@ -217,13 +218,13 @@ public override T Get() } if (obj == null) { - obj = new T(); + throw new InvalidOperationException("Type not registered with type tool"); } obj.Init(this); return obj; } - public override void Return(T obj) + public override void Return(ref T obj) { if (obj == null) { @@ -235,6 +236,7 @@ public override void Return(T obj) { typePool.Return(obj); } + obj = null; } } @@ -514,8 +516,8 @@ public static void KillEdge(IPool pool, Edge eDel) eNext._Sym._next = ePrev; ePrev._Sym._next = eNext; - pool.Return(eDel._Sym); - pool.Return(eDel); + pool.Return(ref eDel._Sym); + pool.Return(ref eDel); } /// @@ -539,7 +541,7 @@ public static void KillVertex(IPool pool, Vertex vDel, Vertex newOrg) vNext._prev = vPrev; vPrev._next = vNext; - pool.Return(vDel); + pool.Return(ref vDel); } /// @@ -563,7 +565,7 @@ public static void KillFace(IPool pool, Face fDel, Face newLFace) fNext._prev = fPrev; fPrev._next = fNext; - pool.Return(fDel); + pool.Return(ref fDel); } /// diff --git a/LibTessDotNet/Sources/PriorityHeap.cs b/LibTessDotNet/Sources/PriorityHeap.cs index f695540..d41f967 100644 --- a/LibTessDotNet/Sources/PriorityHeap.cs +++ b/LibTessDotNet/Sources/PriorityHeap.cs @@ -50,7 +50,7 @@ internal class PriorityHeap where TValue : class { public delegate bool LessOrEqual(TValue lhs, TValue rhs); - protected class HandleElem + internal class HandleElem { internal TValue _key; internal int _node; @@ -78,7 +78,7 @@ public PriorityHeap(int initialSize, LessOrEqual leq) _initialized = false; _nodes[1] = 1; - _handles[1] = new HandleElem { _key = null }; + _handles[1] = new HandleElem(); } private void FloatDown(int curr) diff --git a/LibTessDotNet/Sources/PriorityQueue.cs b/LibTessDotNet/Sources/PriorityQueue.cs index 1f386ae..9668df5 100644 --- a/LibTessDotNet/Sources/PriorityQueue.cs +++ b/LibTessDotNet/Sources/PriorityQueue.cs @@ -65,7 +65,7 @@ public PriorityQueue(int initialSize, PriorityHeap.LessOrEqual leq) _initialized = false; } - class StackItem + struct StackItem { internal int p, r; }; diff --git a/LibTessDotNet/Sources/Sweep.cs b/LibTessDotNet/Sources/Sweep.cs index 02bed87..95648d3 100644 --- a/LibTessDotNet/Sources/Sweep.cs +++ b/LibTessDotNet/Sources/Sweep.cs @@ -58,7 +58,7 @@ public void Init(IPool pool) public void Reset(IPool pool) { _eUp = null; - _nodeUp = null; + _nodeUp = null; // don't return to pool as Dict takes care of that _windingNumber = 0; _inside = false; _sentinel = false; @@ -129,7 +129,7 @@ private void DeleteRegion(ActiveRegion reg) } reg._eUp._activeRegion = null; _dict.Remove(reg._nodeUp); - _pool.Return(reg); + _pool.Return(ref reg); } /// @@ -609,7 +609,7 @@ private bool CheckForIntersect(ActiveRegion regUp) { // Easy case -- intersection at one of the right endpoints CheckForRightSplice(regUp); - _pool.Return(isect); + _pool.Return(ref isect); return false; } @@ -630,7 +630,7 @@ private bool CheckForIntersect(ActiveRegion regUp) eUp = RegionBelow(regUp)._eUp; FinishLeftRegions(RegionBelow(regUp), regLo); AddRightEdges(regUp, eUp._Oprev, eUp, eUp, true); - _pool.Return(isect); + _pool.Return(ref isect); return true; } if( dstUp == _event ) { @@ -643,7 +643,7 @@ private bool CheckForIntersect(ActiveRegion regUp) regLo._eUp = eLo._Oprev; eLo = FinishLeftRegions(regLo, null); AddRightEdges(regUp, eLo._Onext, eUp._Rprev, e, true); - _pool.Return(isect); + _pool.Return(ref isect); return true; } // Special case: called from ConnectRightVertex. If either @@ -664,7 +664,7 @@ private bool CheckForIntersect(ActiveRegion regUp) eLo._Org._t = _event._t; } // leave the rest for ConnectRightVertex - _pool.Return(isect); + _pool.Return(ref isect); return false; } @@ -680,7 +680,7 @@ private bool CheckForIntersect(ActiveRegion regUp) _mesh.Splice(_pool, eLo._Oprev, eUp); eUp._Org._s = isect._s; eUp._Org._t = isect._t; - _pool.Return(isect); + _pool.Return(ref isect); isect = null; eUp._Org._pqHandle = _pq.Insert(eUp._Org); if (eUp._Org._pqHandle._handle == PQHandle.Invalid) @@ -933,7 +933,7 @@ private void ConnectLeftVertex(MeshUtils.Vertex vEvent) // Get a pointer to the active region containing vEvent tmp._eUp = vEvent._anEdge._Sym; var regUp = _dict.Find(tmp).Key; - _pool.Return(tmp); + _pool.Return(ref tmp); var regLo = RegionBelow(regUp); if (regLo == null) { @@ -1064,7 +1064,14 @@ private void AddSentinel(Real smin, Real smax, Real t) /// private void InitEdgeDict() { - _dict = new Dict(EdgeLeq); + if (_dict == null) + { + _dict = new Dict(_pool, EdgeLeq); + } + else + { + _dict.Init(); + } AddSentinel(-SentinelCoord, SentinelCoord, -SentinelCoord); AddSentinel(-SentinelCoord, SentinelCoord, +SentinelCoord); @@ -1089,7 +1096,8 @@ private void DoneEdgeDict() DeleteRegion(reg); } - _dict = null; + Debug.Assert(_dict.Empty); + _dict.Reset(); } /// @@ -1149,8 +1157,11 @@ private void InitPriorityQ() } // Make sure there is enough space for sentinels. vertexCount += 8; - - _pq = new PriorityQueue(vertexCount, Geom.VertLeq); + + if (_pq == null) + { + _pq = new PriorityQueue(vertexCount, Geom.VertLeq); + } vHead = _mesh._vHead; for( v = vHead._next; v != vHead; v = v._next ) { @@ -1165,7 +1176,7 @@ private void InitPriorityQ() private void DonePriorityQ() { - _pq = null; + Debug.Assert(_pq.Empty); } /// diff --git a/LibTessDotNet/Sources/Tess.cs b/LibTessDotNet/Sources/Tess.cs index aba250e..f1a113f 100644 --- a/LibTessDotNet/Sources/Tess.cs +++ b/LibTessDotNet/Sources/Tess.cs @@ -199,15 +199,16 @@ public Tess() } public Tess(IPool pool) { + if (pool == null) + { + throw new ArgumentNullException("pool"); + } + _normal = Vec3.Zero; _bminX = _bminY = _bmaxX = _bmaxY = 0; _windingRule = WindingRule.EvenOdd; _pool = pool; - if (_pool == null) - { - _pool = new NullPool(); - } _mesh = null; _vertices = null; @@ -845,8 +846,7 @@ public void Tessellate(WindingRule windingRule = WindingRule.EvenOdd, ElementTyp OutputPolymesh(elementType, polySize); } - _pool.Return(_mesh); - _mesh = null; + _pool.Return(ref _mesh); } } } diff --git a/TessBed/UnitTests.cs b/TessBed/UnitTests.cs index 239fcce..cfa5b0d 100644 --- a/TessBed/UnitTests.cs +++ b/TessBed/UnitTests.cs @@ -1,37 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.IO; -using NUnit.Framework; -using LibTessDotNet; -using System.Reflection; - -namespace TessBed -{ - [TestFixture] - public class UnitTests - { - static DataLoader _loader = new DataLoader(); - - public struct TestCaseData - { - public DataLoader.Asset Asset; - public WindingRule Winding; - public int ElementSize; - - public override string ToString() - { - return string.Format("{0}, {1}, {2}", Winding, Asset.Name, ElementSize); - } - } - - public class TestData - { - public int ElementSize; - public int[] Indices; - } - +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using NUnit.Framework; +using LibTessDotNet; +using System.Reflection; + +namespace TessBed +{ + [TestFixture] + public class UnitTests + { + static DataLoader _loader = new DataLoader(); + + public struct TestCaseData + { + public DataLoader.Asset Asset; + public WindingRule Winding; + public int ElementSize; + + public override string ToString() + { + return string.Format("{0}, {1}, {2}", Winding, Asset.Name, ElementSize); + } + } + + public class TestData + { + public int ElementSize; + public int[] Indices; + } + public class TestPool : IPool { private IDictionary _newCount = new Dictionary(); @@ -53,7 +53,7 @@ public override void Register(ITypePool typePool) { } - public override void Return(T obj) + public override void Return(ref T obj) { if (obj == null) { @@ -69,6 +69,7 @@ public override void Return(T obj) { throw new InvalidOperationException(); } + obj = null; } public void AssertCounts() @@ -78,276 +79,276 @@ public void AssertCounts() Assert.AreEqual(type.Value, _freeCount[type.Key], type.Key.ToString()); } } - } - - public bool OutputTestData = false; - public static string TestDataPath = Path.Combine("..", "..", "TessBed", "TestData"); - - - [Test] - public void Tesselate_WithSingleTriangle_ProducesSameTriangle() - { - string data = "0,0,0\n0,1,0\n1,1,0"; - var indices = new List(); - var expectedIndices = new int[] { 0, 1, 2 }; - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) - { - var pset = DataLoader.LoadDat(stream); - var pool = new TestPool(); - var tess = new Tess(pool); - - PolyConvert.ToTess(pset, tess); - tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); - - indices.Clear(); - for (int i = 0; i < tess.ElementCount; i++) - { - for (int j = 0; j < 3; j++) - { - int index = tess.Elements[i * 3 + j]; - indices.Add(index); - } - } - - Assert.AreEqual(expectedIndices, indices.ToArray()); - pool.AssertCounts(); - } - } - - [Test] - // From https://github.com/memononen/libtess2/issues/14 - public void Tesselate_WithThinQuad_DoesNotCrash() - { - string data = "9.5,7.5,-0.5\n9.5,2,-0.5\n9.5,2,-0.4999999701976776123\n9.5,7.5,-0.4999999701976776123"; - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) - { - var pset = DataLoader.LoadDat(stream); - var tess = new Tess(); - PolyConvert.ToTess(pset, tess); - tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); - } - } - - [Test] - // From https://github.com/speps/LibTessDotNet/issues/1 - public void Tesselate_WithIssue1Quad_ReturnsSameResultAsLibtess2() - { - string data = "50,50\n300,50\n300,200\n50,200"; - var indices = new List(); - var expectedIndices = new int[] { 0, 1, 2, 1, 0, 3 }; - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) - { - var pset = DataLoader.LoadDat(stream); - var tess = new Tess(); - PolyConvert.ToTess(pset, tess); - tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); - indices.Clear(); - for (int i = 0; i < tess.ElementCount; i++) - { - for (int j = 0; j < 3; j++) - { - int index = tess.Elements[i * 3 + j]; - indices.Add(index); - } - } - Assert.AreEqual(expectedIndices, indices.ToArray()); - } - } - - [Test] - // From https://github.com/speps/LibTessDotNet/issues/1 - public void Tesselate_WithNoEmptyPolygonsTrue_RemovesEmptyPolygons() - { - string data = "2,0,4\n2,0,2\n4,0,2\n4,0,0\n0,0,0\n0,0,4"; - var indices = new List(); - var expectedIndices = new int[] { 0, 1, 2, 2, 3, 4, 3, 1, 5 }; - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) - { - var pset = DataLoader.LoadDat(stream); - var tess = new Tess(); - PolyConvert.ToTess(pset, tess); - tess.NoEmptyPolygons = true; - tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); - indices.Clear(); - for (int i = 0; i < tess.ElementCount; i++) - { - for (int j = 0; j < 3; j++) - { - int index = tess.Elements[i * 3 + j]; - indices.Add(index); - } - } - Assert.AreEqual(expectedIndices, indices.ToArray()); - } - } - - [Test] - public void Tesselate_CalledTwiceOnSameInstance_DoesNotCrash() - { - string data = "0,0,0\n0,1,0\n1,1,0"; - var indices = new List(); - var expectedIndices = new int[] { 0, 1, 2 }; - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) - { - var pset = DataLoader.LoadDat(stream); - var tess = new Tess(); - - // Call once - PolyConvert.ToTess(pset, tess); - tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); - - indices.Clear(); - for (int i = 0; i < tess.ElementCount; i++) - { - for (int j = 0; j < 3; j++) - { - int index = tess.Elements[i * 3 + j]; - indices.Add(index); - } - } - - Assert.AreEqual(expectedIndices, indices.ToArray()); - - // Call twice - PolyConvert.ToTess(pset, tess); - tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); - - indices.Clear(); - for (int i = 0; i < tess.ElementCount; i++) - { - for (int j = 0; j < 3; j++) - { - int index = tess.Elements[i * 3 + j]; - indices.Add(index); - } - } - - Assert.AreEqual(expectedIndices, indices.ToArray()); - } - } - - [Test, TestCaseSource("GetTestCaseData")] - public void Tessellate_WithAsset_ReturnsExpectedTriangulation(TestCaseData data) - { - var pset = data.Asset.Polygons; - var pool = new TestPool(); - var tess = new Tess(pool); - PolyConvert.ToTess(pset, tess); - tess.Tessellate(data.Winding, ElementType.Polygons, data.ElementSize); - - var resourceName = Assembly.GetExecutingAssembly().GetName().Name + ".TestData." + data.Asset.Name + ".testdat"; - var testData = ParseTestData(data.Winding, data.ElementSize, Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)); - Assert.IsNotNull(testData); - Assert.AreEqual(testData.ElementSize, data.ElementSize); - - var indices = new List(); - for (int i = 0; i < tess.ElementCount; i++) - { - for (int j = 0; j < data.ElementSize; j++) - { - int index = tess.Elements[i * data.ElementSize + j]; - indices.Add(index); - } - } - - Assert.AreEqual(testData.Indices, indices.ToArray()); - pool.AssertCounts(); - } - - public TestData ParseTestData(WindingRule winding, int elementSize, Stream resourceStream) - { - var lines = new List(); - - bool found = false; - using (var stream = new StreamReader(resourceStream)) - { - string line; - while ((line = stream.ReadLine()) != null) - { - line = line.Trim(); - if (found && string.IsNullOrEmpty(line)) - { - break; - } - if (found) - { - lines.Add(line); - } - var parts = line.Split(' '); - if (parts.FirstOrDefault() == winding.ToString() && Int32.Parse(parts.LastOrDefault()) == elementSize) - { - found = true; - } - } - } - var indices = new List(); - foreach (var line in lines) - { - var parts = line.Split(' '); - if (parts.Length != elementSize) - { - continue; - } - foreach (var part in parts) - { - indices.Add(Int32.Parse(part)); - } - } - if (found) - { - return new TestData() - { - ElementSize = elementSize, - Indices = indices.ToArray() - }; - } - return null; - } - - public static void GenerateTestData() - { - foreach (var asset in _loader.Assets) - { - var pset = asset.Polygons; - - var lines = new List(); - var indices = new List(); - - foreach (WindingRule winding in Enum.GetValues(typeof(WindingRule))) - { - var tess = new Tess(); - PolyConvert.ToTess(pset, tess); - tess.Tessellate(winding, ElementType.Polygons, 3); - - lines.Add(string.Format("{0} {1}", winding, 3)); - for (int i = 0; i < tess.ElementCount; i++) - { - indices.Clear(); - for (int j = 0; j < 3; j++) - { - int index = tess.Elements[i * 3 + j]; - indices.Add(index); - } - lines.Add(string.Join(" ", indices)); - } - lines.Add(""); - } - - File.WriteAllLines(Path.Combine(TestDataPath, asset.Name + ".testdat"), lines); - } - } - - public static TestCaseData[] GetTestCaseData() - { - var data = new List(); - foreach (WindingRule winding in Enum.GetValues(typeof(WindingRule))) - { - foreach (var asset in _loader.Assets) - { - data.Add(new TestCaseData { Asset = asset, Winding = winding, ElementSize = 3 }); - } - } - return data.ToArray(); - } - } -} + } + + public bool OutputTestData = false; + public static string TestDataPath = Path.Combine("..", "..", "TessBed", "TestData"); + + + [Test] + public void Tesselate_WithSingleTriangle_ProducesSameTriangle() + { + string data = "0,0,0\n0,1,0\n1,1,0"; + var indices = new List(); + var expectedIndices = new int[] { 0, 1, 2 }; + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) + { + var pset = DataLoader.LoadDat(stream); + var pool = new TestPool(); + var tess = new Tess(pool); + + PolyConvert.ToTess(pset, tess); + tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); + + indices.Clear(); + for (int i = 0; i < tess.ElementCount; i++) + { + for (int j = 0; j < 3; j++) + { + int index = tess.Elements[i * 3 + j]; + indices.Add(index); + } + } + + Assert.AreEqual(expectedIndices, indices.ToArray()); + pool.AssertCounts(); + } + } + + [Test] + // From https://github.com/memononen/libtess2/issues/14 + public void Tesselate_WithThinQuad_DoesNotCrash() + { + string data = "9.5,7.5,-0.5\n9.5,2,-0.5\n9.5,2,-0.4999999701976776123\n9.5,7.5,-0.4999999701976776123"; + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) + { + var pset = DataLoader.LoadDat(stream); + var tess = new Tess(); + PolyConvert.ToTess(pset, tess); + tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); + } + } + + [Test] + // From https://github.com/speps/LibTessDotNet/issues/1 + public void Tesselate_WithIssue1Quad_ReturnsSameResultAsLibtess2() + { + string data = "50,50\n300,50\n300,200\n50,200"; + var indices = new List(); + var expectedIndices = new int[] { 0, 1, 2, 1, 0, 3 }; + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) + { + var pset = DataLoader.LoadDat(stream); + var tess = new Tess(); + PolyConvert.ToTess(pset, tess); + tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); + indices.Clear(); + for (int i = 0; i < tess.ElementCount; i++) + { + for (int j = 0; j < 3; j++) + { + int index = tess.Elements[i * 3 + j]; + indices.Add(index); + } + } + Assert.AreEqual(expectedIndices, indices.ToArray()); + } + } + + [Test] + // From https://github.com/speps/LibTessDotNet/issues/1 + public void Tesselate_WithNoEmptyPolygonsTrue_RemovesEmptyPolygons() + { + string data = "2,0,4\n2,0,2\n4,0,2\n4,0,0\n0,0,0\n0,0,4"; + var indices = new List(); + var expectedIndices = new int[] { 0, 1, 2, 2, 3, 4, 3, 1, 5 }; + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) + { + var pset = DataLoader.LoadDat(stream); + var tess = new Tess(); + PolyConvert.ToTess(pset, tess); + tess.NoEmptyPolygons = true; + tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); + indices.Clear(); + for (int i = 0; i < tess.ElementCount; i++) + { + for (int j = 0; j < 3; j++) + { + int index = tess.Elements[i * 3 + j]; + indices.Add(index); + } + } + Assert.AreEqual(expectedIndices, indices.ToArray()); + } + } + + [Test] + public void Tesselate_CalledTwiceOnSameInstance_DoesNotCrash() + { + string data = "0,0,0\n0,1,0\n1,1,0"; + var indices = new List(); + var expectedIndices = new int[] { 0, 1, 2 }; + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) + { + var pset = DataLoader.LoadDat(stream); + var tess = new Tess(); + + // Call once + PolyConvert.ToTess(pset, tess); + tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); + + indices.Clear(); + for (int i = 0; i < tess.ElementCount; i++) + { + for (int j = 0; j < 3; j++) + { + int index = tess.Elements[i * 3 + j]; + indices.Add(index); + } + } + + Assert.AreEqual(expectedIndices, indices.ToArray()); + + // Call twice + PolyConvert.ToTess(pset, tess); + tess.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3); + + indices.Clear(); + for (int i = 0; i < tess.ElementCount; i++) + { + for (int j = 0; j < 3; j++) + { + int index = tess.Elements[i * 3 + j]; + indices.Add(index); + } + } + + Assert.AreEqual(expectedIndices, indices.ToArray()); + } + } + + [Test, TestCaseSource("GetTestCaseData")] + public void Tessellate_WithAsset_ReturnsExpectedTriangulation(TestCaseData data) + { + var pset = data.Asset.Polygons; + var pool = new TestPool(); + var tess = new Tess(pool); + PolyConvert.ToTess(pset, tess); + tess.Tessellate(data.Winding, ElementType.Polygons, data.ElementSize); + + var resourceName = Assembly.GetExecutingAssembly().GetName().Name + ".TestData." + data.Asset.Name + ".testdat"; + var testData = ParseTestData(data.Winding, data.ElementSize, Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)); + Assert.IsNotNull(testData); + Assert.AreEqual(testData.ElementSize, data.ElementSize); + + var indices = new List(); + for (int i = 0; i < tess.ElementCount; i++) + { + for (int j = 0; j < data.ElementSize; j++) + { + int index = tess.Elements[i * data.ElementSize + j]; + indices.Add(index); + } + } + + Assert.AreEqual(testData.Indices, indices.ToArray()); + pool.AssertCounts(); + } + + public TestData ParseTestData(WindingRule winding, int elementSize, Stream resourceStream) + { + var lines = new List(); + + bool found = false; + using (var stream = new StreamReader(resourceStream)) + { + string line; + while ((line = stream.ReadLine()) != null) + { + line = line.Trim(); + if (found && string.IsNullOrEmpty(line)) + { + break; + } + if (found) + { + lines.Add(line); + } + var parts = line.Split(' '); + if (parts.FirstOrDefault() == winding.ToString() && Int32.Parse(parts.LastOrDefault()) == elementSize) + { + found = true; + } + } + } + var indices = new List(); + foreach (var line in lines) + { + var parts = line.Split(' '); + if (parts.Length != elementSize) + { + continue; + } + foreach (var part in parts) + { + indices.Add(Int32.Parse(part)); + } + } + if (found) + { + return new TestData() + { + ElementSize = elementSize, + Indices = indices.ToArray() + }; + } + return null; + } + + public static void GenerateTestData() + { + foreach (var asset in _loader.Assets) + { + var pset = asset.Polygons; + + var lines = new List(); + var indices = new List(); + + foreach (WindingRule winding in Enum.GetValues(typeof(WindingRule))) + { + var tess = new Tess(); + PolyConvert.ToTess(pset, tess); + tess.Tessellate(winding, ElementType.Polygons, 3); + + lines.Add(string.Format("{0} {1}", winding, 3)); + for (int i = 0; i < tess.ElementCount; i++) + { + indices.Clear(); + for (int j = 0; j < 3; j++) + { + int index = tess.Elements[i * 3 + j]; + indices.Add(index); + } + lines.Add(string.Join(" ", indices)); + } + lines.Add(""); + } + + File.WriteAllLines(Path.Combine(TestDataPath, asset.Name + ".testdat"), lines); + } + } + + public static TestCaseData[] GetTestCaseData() + { + var data = new List(); + foreach (WindingRule winding in Enum.GetValues(typeof(WindingRule))) + { + foreach (var asset in _loader.Assets) + { + data.Add(new TestCaseData { Asset = asset, Winding = winding, ElementSize = 3 }); + } + } + return data.ToArray(); + } + } +}