/* ** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) ** Copyright (C) 2011 Silicon Graphics, Inc. ** All Rights Reserved. ** ** Permission is hereby granted, free of charge, to any person obtaining a copy ** of this software and associated documentation files (the "Software"), to deal ** in the Software without restriction, including without limitation the rights ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies ** of the Software, and to permit persons to whom the Software is furnished to do so, ** subject to the following conditions: ** ** The above copyright notice including the dates of first publication and either this ** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be ** included in all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, ** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A ** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. ** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE ** OR OTHER DEALINGS IN THE SOFTWARE. ** ** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not ** be used in advertising or otherwise to promote the sale, use or other dealings in ** this Software without prior written authorization from Silicon Graphics, Inc. */ /* ** Original Author: Eric Veach, July 1994. ** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/. ** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet */ using System; using System.Diagnostics; namespace UnityEngine.Rendering.Universal { using Real = System.Single; namespace LibTessDotNet { internal enum WindingRule { EvenOdd, NonZero, Positive, Negative, AbsGeqTwo } internal enum ElementType { Polygons, ConnectedPolygons, BoundaryContours } internal enum ContourOrientation { Original, Clockwise, CounterClockwise } internal struct ContourVertex { public Vec3 Position; public object Data; public override string ToString() { return string.Format("{0}, {1}", Position, Data); } } internal delegate object CombineCallback(Vec3 position, object[] data, Real[] weights); internal partial class Tess { private Mesh _mesh; private Vec3 _normal; private Vec3 _sUnit; private Vec3 _tUnit; private Real _bminX, _bminY, _bmaxX, _bmaxY; private WindingRule _windingRule; private Dict _dict; private PriorityQueue _pq; private MeshUtils.Vertex _event; private CombineCallback _combineCallback; private ContourVertex[] _vertices; private int _vertexCount; private int[] _elements; private int _elementCount; public Vec3 Normal { get { return _normal; } set { _normal = value; } } public Real SUnitX = 1; public Real SUnitY = 0; #if DOUBLE public Real SentinelCoord = 4e150; #else public Real SentinelCoord = 4e30f; #endif /// /// If true, will remove empty (zero area) polygons. /// public bool NoEmptyPolygons = false; /// /// If true, will use pooling to reduce GC (compare performance with/without, can vary wildly). /// public bool UsePooling = false; public ContourVertex[] Vertices { get { return _vertices; } } public int VertexCount { get { return _vertexCount; } } public int[] Elements { get { return _elements; } } public int ElementCount { get { return _elementCount; } } public Tess() { _normal = Vec3.Zero; _bminX = _bminY = _bmaxX = _bmaxY = 0; _windingRule = WindingRule.EvenOdd; _mesh = null; _vertices = null; _vertexCount = 0; _elements = null; _elementCount = 0; } private void ComputeNormal(ref Vec3 norm) { var v = _mesh._vHead._next; var minVal = new Real[3] { v._coords.X, v._coords.Y, v._coords.Z }; var minVert = new MeshUtils.Vertex[3] { v, v, v }; var maxVal = new Real[3] { v._coords.X, v._coords.Y, v._coords.Z }; var maxVert = new MeshUtils.Vertex[3] { v, v, v }; for (; v != _mesh._vHead; v = v._next) { if (v._coords.X < minVal[0]) { minVal[0] = v._coords.X; minVert[0] = v; } if (v._coords.Y < minVal[1]) { minVal[1] = v._coords.Y; minVert[1] = v; } if (v._coords.Z < minVal[2]) { minVal[2] = v._coords.Z; minVert[2] = v; } if (v._coords.X > maxVal[0]) { maxVal[0] = v._coords.X; maxVert[0] = v; } if (v._coords.Y > maxVal[1]) { maxVal[1] = v._coords.Y; maxVert[1] = v; } if (v._coords.Z > maxVal[2]) { maxVal[2] = v._coords.Z; maxVert[2] = v; } } // Find two vertices separated by at least 1/sqrt(3) of the maximum // distance between any two vertices int i = 0; if (maxVal[1] - minVal[1] > maxVal[0] - minVal[0]) { i = 1; } if (maxVal[2] - minVal[2] > maxVal[i] - minVal[i]) { i = 2; } if (minVal[i] >= maxVal[i]) { // All vertices are the same -- normal doesn't matter norm = new Vec3 { X = 0, Y = 0, Z = 1 }; return; } // Look for a third vertex which forms the triangle with maximum area // (Length of normal == twice the triangle area) Real maxLen2 = 0, tLen2; var v1 = minVert[i]; var v2 = maxVert[i]; Vec3 d1, d2, tNorm; Vec3.Sub(ref v1._coords, ref v2._coords, out d1); for (v = _mesh._vHead._next; v != _mesh._vHead; v = v._next) { Vec3.Sub(ref v._coords, ref v2._coords, out d2); tNorm.X = d1.Y * d2.Z - d1.Z * d2.Y; tNorm.Y = d1.Z * d2.X - d1.X * d2.Z; tNorm.Z = d1.X * d2.Y - d1.Y * d2.X; tLen2 = tNorm.X * tNorm.X + tNorm.Y * tNorm.Y + tNorm.Z * tNorm.Z; if (tLen2 > maxLen2) { maxLen2 = tLen2; norm = tNorm; } } if (maxLen2 <= 0.0f) { // All points lie on a single line -- any decent normal will do norm = Vec3.Zero; i = Vec3.LongAxis(ref d1); norm[i] = 1; } } private void CheckOrientation() { // When we compute the normal automatically, we choose the orientation // so that the the sum of the signed areas of all contours is non-negative. Real area = 0.0f; for (var f = _mesh._fHead._next; f != _mesh._fHead; f = f._next) { if (f._anEdge._winding <= 0) { continue; } area += MeshUtils.FaceArea(f); } if (area < 0.0f) { // Reverse the orientation by flipping all the t-coordinates for (var v = _mesh._vHead._next; v != _mesh._vHead; v = v._next) { v._t = -v._t; } Vec3.Neg(ref _tUnit); } } private void ProjectPolygon() { var norm = _normal; bool computedNormal = false; if (norm.X == 0.0f && norm.Y == 0.0f && norm.Z == 0.0f) { ComputeNormal(ref norm); _normal = norm; computedNormal = true; } int i = Vec3.LongAxis(ref norm); _sUnit[i] = 0; _sUnit[(i + 1) % 3] = SUnitX; _sUnit[(i + 2) % 3] = SUnitY; _tUnit[i] = 0; _tUnit[(i + 1) % 3] = norm[i] > 0.0f ? -SUnitY : SUnitY; _tUnit[(i + 2) % 3] = norm[i] > 0.0f ? SUnitX : -SUnitX; // Project the vertices onto the sweep plane for (var v = _mesh._vHead._next; v != _mesh._vHead; v = v._next) { Vec3.Dot(ref v._coords, ref _sUnit, out v._s); Vec3.Dot(ref v._coords, ref _tUnit, out v._t); } if (computedNormal) { CheckOrientation(); } // Compute ST bounds. bool first = true; for (var v = _mesh._vHead._next; v != _mesh._vHead; v = v._next) { if (first) { _bminX = _bmaxX = v._s; _bminY = _bmaxY = v._t; first = false; } else { if (v._s < _bminX) _bminX = v._s; if (v._s > _bmaxX) _bmaxX = v._s; if (v._t < _bminY) _bminY = v._t; if (v._t > _bmaxY) _bmaxY = v._t; } } } /// /// TessellateMonoRegion( face ) tessellates a monotone region /// (what else would it do??) The region must consist of a single /// loop of half-edges (see mesh.h) oriented CCW. "Monotone" in this /// case means that any vertical line intersects the interior of the /// region in a single interval. /// /// Tessellation consists of adding interior edges (actually pairs of /// half-edges), to split the region into non-overlapping triangles. /// /// The basic idea is explained in Preparata and Shamos (which I don't /// have handy right now), although their implementation is more /// complicated than this one. The are two edge chains, an upper chain /// and a lower chain. We process all vertices from both chains in order, /// from right to left. /// /// The algorithm ensures that the following invariant holds after each /// vertex is processed: the untessellated region consists of two /// chains, where one chain (say the upper) is a single edge, and /// the other chain is concave. The left vertex of the single edge /// is always to the left of all vertices in the concave chain. /// /// Each step consists of adding the rightmost unprocessed vertex to one /// of the two chains, and forming a fan of triangles from the rightmost /// of two chain endpoints. Determining whether we can add each triangle /// to the fan is a simple orientation test. By making the fan as large /// as possible, we restore the invariant (check it yourself). /// private void TessellateMonoRegion(MeshUtils.Face face) { // All edges are oriented CCW around the boundary of the region. // First, find the half-edge whose origin vertex is rightmost. // Since the sweep goes from left to right, face->anEdge should // be close to the edge we want. var up = face._anEdge; Debug.Assert(up._Lnext != up && up._Lnext._Lnext != up); while (Geom.VertLeq(up._Dst, up._Org)) up = up._Lprev; while (Geom.VertLeq(up._Org, up._Dst)) up = up._Lnext; var lo = up._Lprev; while (up._Lnext != lo) { if (Geom.VertLeq(up._Dst, lo._Org)) { // up.Dst is on the left. It is safe to form triangles from lo.Org. // The EdgeGoesLeft test guarantees progress even when some triangles // are CW, given that the upper and lower chains are truly monotone. while (lo._Lnext != up && (Geom.EdgeGoesLeft(lo._Lnext) || Geom.EdgeSign(lo._Org, lo._Dst, lo._Lnext._Dst) <= 0.0f)) { lo = _mesh.Connect(lo._Lnext, lo)._Sym; } lo = lo._Lprev; } else { // lo.Org is on the left. We can make CCW triangles from up.Dst. while (lo._Lnext != up && (Geom.EdgeGoesRight(up._Lprev) || Geom.EdgeSign(up._Dst, up._Org, up._Lprev._Org) >= 0.0f)) { up = _mesh.Connect(up, up._Lprev)._Sym; } up = up._Lnext; } } // Now lo.Org == up.Dst == the leftmost vertex. The remaining region // can be tessellated in a fan from this leftmost vertex. Debug.Assert(lo._Lnext != up); while (lo._Lnext._Lnext != up) { lo = _mesh.Connect(lo._Lnext, lo)._Sym; } } /// /// TessellateInterior( mesh ) tessellates each region of /// the mesh which is marked "inside" the polygon. Each such region /// must be monotone. /// private void TessellateInterior() { MeshUtils.Face f, next; for (f = _mesh._fHead._next; f != _mesh._fHead; f = next) { // Make sure we don't try to tessellate the new triangles. next = f._next; if (f._inside) { TessellateMonoRegion(f); } } } /// /// DiscardExterior zaps (ie. sets to null) all faces /// which are not marked "inside" the polygon. Since further mesh operations /// on NULL faces are not allowed, the main purpose is to clean up the /// mesh so that exterior loops are not represented in the data structure. /// private void DiscardExterior() { MeshUtils.Face f, next; for (f = _mesh._fHead._next; f != _mesh._fHead; f = next) { // Since f will be destroyed, save its next pointer. next = f._next; if (!f._inside) { _mesh.ZapFace(f); } } } /// /// SetWindingNumber( value, keepOnlyBoundary ) resets the /// winding numbers on all edges so that regions marked "inside" the /// polygon have a winding number of "value", and regions outside /// have a winding number of 0. /// /// If keepOnlyBoundary is TRUE, it also deletes all edges which do not /// separate an interior region from an exterior one. /// private void SetWindingNumber(int value, bool keepOnlyBoundary) { MeshUtils.Edge e, eNext; for (e = _mesh._eHead._next; e != _mesh._eHead; e = eNext) { eNext = e._next; if (e._Rface._inside != e._Lface._inside) { /* This is a boundary edge (one side is interior, one is exterior). */ e._winding = (e._Lface._inside) ? value : -value; } else { /* Both regions are interior, or both are exterior. */ if (!keepOnlyBoundary) { e._winding = 0; } else { _mesh.Delete(e); } } } } private int GetNeighbourFace(MeshUtils.Edge edge) { if (edge._Rface == null) return MeshUtils.Undef; if (!edge._Rface._inside) return MeshUtils.Undef; return edge._Rface._n; } private void OutputPolymesh(ElementType elementType, int polySize) { MeshUtils.Vertex v; MeshUtils.Face f; MeshUtils.Edge edge; int maxFaceCount = 0; int maxVertexCount = 0; int faceVerts, i; if (polySize < 3) { polySize = 3; } // Assume that the input data is triangles now. // Try to merge as many polygons as possible if (polySize > 3) { _mesh.MergeConvexFaces(polySize); } // Mark unused for (v = _mesh._vHead._next; v != _mesh._vHead; v = v._next) v._n = MeshUtils.Undef; // Create unique IDs for all vertices and faces. for (f = _mesh._fHead._next; f != _mesh._fHead; f = f._next) { f._n = MeshUtils.Undef; if (!f._inside) continue; if (NoEmptyPolygons) { var area = MeshUtils.FaceArea(f); if (Math.Abs(area) < Real.Epsilon) { continue; } } edge = f._anEdge; faceVerts = 0; do { v = edge._Org; if (v._n == MeshUtils.Undef) { v._n = maxVertexCount; maxVertexCount++; } faceVerts++; edge = edge._Lnext; } while (edge != f._anEdge); Debug.Assert(faceVerts <= polySize); f._n = maxFaceCount; ++maxFaceCount; } _elementCount = maxFaceCount; if (elementType == ElementType.ConnectedPolygons) maxFaceCount *= 2; _elements = new int[maxFaceCount * polySize]; _vertexCount = maxVertexCount; _vertices = new ContourVertex[_vertexCount]; // Output vertices. for (v = _mesh._vHead._next; v != _mesh._vHead; v = v._next) { if (v._n != MeshUtils.Undef) { // Store coordinate _vertices[v._n].Position = v._coords; _vertices[v._n].Data = v._data; } } // Output indices. int elementIndex = 0; for (f = _mesh._fHead._next; f != _mesh._fHead; f = f._next) { if (!f._inside) continue; if (NoEmptyPolygons) { var area = MeshUtils.FaceArea(f); if (Math.Abs(area) < Real.Epsilon) { continue; } } // Store polygon edge = f._anEdge; faceVerts = 0; do { v = edge._Org; _elements[elementIndex++] = v._n; faceVerts++; edge = edge._Lnext; } while (edge != f._anEdge); // Fill unused. for (i = faceVerts; i < polySize; ++i) { _elements[elementIndex++] = MeshUtils.Undef; } // Store polygon connectivity if (elementType == ElementType.ConnectedPolygons) { edge = f._anEdge; do { _elements[elementIndex++] = GetNeighbourFace(edge); edge = edge._Lnext; } while (edge != f._anEdge); // Fill unused. for (i = faceVerts; i < polySize; ++i) { _elements[elementIndex++] = MeshUtils.Undef; } } } } private void OutputContours() { MeshUtils.Face f; MeshUtils.Edge edge, start; int startVert = 0; int vertCount = 0; _vertexCount = 0; _elementCount = 0; for (f = _mesh._fHead._next; f != _mesh._fHead; f = f._next) { if (!f._inside) continue; start = edge = f._anEdge; do { ++_vertexCount; edge = edge._Lnext; } while (edge != start); ++_elementCount; } _elements = new int[_elementCount * 2]; _vertices = new ContourVertex[_vertexCount]; int vertIndex = 0; int elementIndex = 0; startVert = 0; for (f = _mesh._fHead._next; f != _mesh._fHead; f = f._next) { if (!f._inside) continue; vertCount = 0; start = edge = f._anEdge; do { _vertices[vertIndex].Position = edge._Org._coords; _vertices[vertIndex].Data = edge._Org._data; ++vertIndex; ++vertCount; edge = edge._Lnext; } while (edge != start); _elements[elementIndex++] = startVert; _elements[elementIndex++] = vertCount; startVert += vertCount; } } private Real SignedArea(ContourVertex[] vertices) { Real area = 0.0f; for (int i = 0; i < vertices.Length; i++) { var v0 = vertices[i]; var v1 = vertices[(i + 1) % vertices.Length]; area += v0.Position.X * v1.Position.Y; area -= v0.Position.Y * v1.Position.X; } return 0.5f * area; } public void AddContour(ContourVertex[] vertices) { AddContour(vertices, ContourOrientation.Original); } public void AddContour(ContourVertex[] vertices, ContourOrientation forceOrientation) { if (_mesh == null) { _mesh = new Mesh(); } bool reverse = false; if (forceOrientation != ContourOrientation.Original) { var area = SignedArea(vertices); reverse = (forceOrientation == ContourOrientation.Clockwise && area < 0.0f) || (forceOrientation == ContourOrientation.CounterClockwise && area > 0.0f); } MeshUtils.Edge e = null; for (int i = 0; i < vertices.Length; ++i) { if (e == null) { e = _mesh.MakeEdge(); _mesh.Splice(e, e._Sym); } else { // Create a new vertex and edge which immediately follow e // in the ordering around the left face. _mesh.SplitEdge(e); e = e._Lnext; } int index = reverse ? vertices.Length - 1 - i : i; // The new vertex is now e._Org. e._Org._coords = vertices[index].Position; e._Org._data = vertices[index].Data; // The winding of an edge says how the winding number changes as we // cross from the edge's right face to its left face. We add the // vertices in such an order that a CCW contour will add +1 to // the winding number of the region inside the contour. e._winding = 1; e._Sym._winding = -1; } } public void Tessellate(WindingRule windingRule, ElementType elementType, int polySize) { Tessellate(windingRule, elementType, polySize, null); } public void Tessellate(WindingRule windingRule, ElementType elementType, int polySize, CombineCallback combineCallback) { _normal = Vec3.Zero; _vertices = null; _elements = null; _windingRule = windingRule; _combineCallback = combineCallback; if (_mesh == null) { return; } // Determine the polygon normal and project vertices onto the plane // of the polygon. ProjectPolygon(); // ComputeInterior computes the planar arrangement specified // by the given contours, and further subdivides this arrangement // into regions. Each region is marked "inside" if it belongs // to the polygon, according to the rule given by windingRule. // Each interior region is guaranteed be monotone. ComputeInterior(); // If the user wants only the boundary contours, we throw away all edges // except those which separate the interior from the exterior. // Otherwise we tessellate all the regions marked "inside". if (elementType == ElementType.BoundaryContours) { SetWindingNumber(1, true); } else { TessellateInterior(); } _mesh.Check(); if (elementType == ElementType.BoundaryContours) { OutputContours(); } else { OutputPolymesh(elementType, polySize); } if (UsePooling) { _mesh.Free(); } _mesh = null; } } } }