internal static Edge[] BuildEdges(int vertexCount, int[] triangleArray)
        {
            int maxEdgeCount = triangleArray.Length;
            int[] firstEdge = new int[vertexCount + maxEdgeCount];
            int nextEdge = vertexCount;
            int triangleCount = triangleArray.Length / 3;

            for (int a = 0; a < vertexCount; a++)
                firstEdge[a] = -1;

            // First pass over all triangles. This finds all the edges satisfying the
            // condition that the first vertex index is less than the second vertex index
            // when the direction from the first vertex to the second vertex represents
            // a counterclockwise winding around the triangle to which the edge belongs.
            // For each edge found, the edge index is stored in a linked list of edges
            // belonging to the lower-numbered vertex index i. This allows us to quickly
            // find an edge in the second pass whose higher-numbered vertex index is i.
            Edge[] edgeArray = new Edge[maxEdgeCount];

            int edgeCount = 0;
            for (int a = 0; a < triangleCount; a++) {
                int i1 = triangleArray[a * 3 + 2];
                for (int b = 0; b < 3; b++) {
                    int i2 = triangleArray[a * 3 + b];
                    if (i1 < i2) {
                        Edge newEdge = new Edge();
                        newEdge.vertexIndex[0] = i1;
                        newEdge.vertexIndex[1] = i2;
                        newEdge.faceIndex[0] = a;
                        newEdge.faceIndex[1] = a;
                        edgeArray[edgeCount] = newEdge;

                        int edgeIndex = firstEdge[i1];
                        if (edgeIndex == -1) {
                            firstEdge[i1] = edgeCount;
                        }
                        else {
                            while (true) {
                                int index = firstEdge[nextEdge + edgeIndex];
                                if (index == -1) {
                                    firstEdge[nextEdge + edgeIndex] = edgeCount;
                                    break;
                                }

                                edgeIndex = index;
                            }
                        }

                        firstEdge[nextEdge + edgeCount] = -1;
                        edgeCount++;
                    }

                    i1 = i2;
                }
            }

            // Second pass over all triangles. This finds all the edges satisfying the
            // condition that the first vertex index is greater than the second vertex index
            // when the direction from the first vertex to the second vertex represents
            // a counterclockwise winding around the triangle to which the edge belongs.
            // For each of these edges, the same edge should have already been found in
            // the first pass for a different triangle. Of course we might have edges with only one triangle
            // in that case we just add the edge here
            // So we search the list of edges
            // for the higher-numbered vertex index for the matching edge and fill in the
            // second triangle index. The maximum number of comparisons in this search for
            // any vertex is the number of edges having that vertex as an endpoint.

            for (int a = 0; a < triangleCount; a++) {
                int i1 = triangleArray[a * 3 + 2];
                for (int b = 0; b < 3; b++) {
                    int i2 = triangleArray[a * 3 + b];
                    if (i1 > i2) {
                        bool foundEdge = false;
                        for (int edgeIndex = firstEdge[i2]; edgeIndex != -1; edgeIndex = firstEdge[nextEdge + edgeIndex]) {
                            Edge edge = edgeArray[edgeIndex];
                            if ((edge.vertexIndex[1] == i1) && (edge.faceIndex[0] == edge.faceIndex[1])) {
                                edgeArray[edgeIndex].faceIndex[1] = a;
                                foundEdge = true;
                                break;
                            }
                        }

                        if (!foundEdge) {
                            Edge newEdge = new Edge();
                            newEdge.vertexIndex[0] = i1;
                            newEdge.vertexIndex[1] = i2;
                            newEdge.faceIndex[0] = a;
                            newEdge.faceIndex[1] = a;
                            edgeArray[edgeCount] = newEdge;
                            edgeCount++;
                        }
                    }

                    i1 = i2;
                }
            }

            Edge[] compactedEdges = new Edge[edgeCount];
            for (int e = 0; e < edgeCount; e++)
                compactedEdges[e] = edgeArray[e];

            return compactedEdges;
        }
        internal static EdgeLoop[] BuildEdgeLoops(Edge[] manifoldEdges)
        {
            List<EdgeLoop> loops = new List<EdgeLoop>();
            if (manifoldEdges.Length == 0) return loops.ToArray();
            List<Edge> Stock = new List<Edge>(manifoldEdges);

            List<int> Loop = new List<int>();

            int nextVI;
            Loop.Add(Stock[0].vertexIndex[0]);
            Loop.Add(Stock[0].vertexIndex[1]);
            int curVI = Stock[0].vertexIndex[1];
            Stock.RemoveAt(0);

            while (Stock.Count > 0) {
                // find edge that connects to curVI
                Edge E = getNextEdge(curVI, ref Stock, out nextVI);
                if (E == null)
                    return new EdgeLoop[0]; // Open Loop
                Loop.Add(nextVI);
                curVI = nextVI;
                Stock.Remove(E);

                if (curVI == Loop[0]) {
                    loops.Add(new EdgeLoop(Loop));
                    Loop.Clear();
                    if (Stock.Count > 0) {
                        Loop.Add(Stock[0].vertexIndex[0]);
                        Loop.Add(Stock[0].vertexIndex[1]);
                        curVI = Stock[0].vertexIndex[1];
                        Stock.RemoveAt(0);
                    }
                }
            }
            if (Loop.Count > 0)
                loops.Add(new EdgeLoop(Loop));

            return loops.ToArray();
        }