private Triangle[] Triangulate(List <Vertex> verts)
    {
        //Randomize vertices for faster average triangulation
        Algorithm.Shuffle <Vertex>(ref verts);

        //Construct circle enclosing all the vertices
        Vector2 centroid = Vector2.zero;

        foreach (Vertex vert in verts)
        {
            centroid += vert.p;
        }
        centroid /= verts.Count;
        float r = 0.0f;

        foreach (Vertex vert in verts)
        {
            r = Mathf.Max(r, Vector2.Distance(vert.p, centroid));
        }
        //Add some padding to avoid floating point errors later on
        r *= 1.2f;

        Vector2 pi0 = new Vector2(centroid.x, centroid.y - 2 * r);
        Vector2 pi1 = new Vector2(centroid.x + r * Mathf.Sqrt(3), centroid.y + r);
        Vector2 pi2 = new Vector2(centroid.x - r * Mathf.Sqrt(3), centroid.y + r);

        Debug.DrawLine(pi0, pi1, Color.cyan, 5.0f, false);
        Debug.DrawLine(pi1, pi2, Color.cyan, 5.0f, false);
        Debug.DrawLine(pi2, pi0, Color.cyan, 5.0f, false);

        Vertex vi0 = new Vertex(pi0, -1);
        Vertex vi1 = new Vertex(pi1, -1);
        Vertex vi2 = new Vertex(pi2, -1);

        //Imaginary triangle
        HalfEdge e01Imag  = new HalfEdge(vi0);
        HalfEdge e12Imag  = new HalfEdge(vi1);
        HalfEdge e20Imag  = new HalfEdge(vi2);
        Triangle treeRoot = new Triangle(e01Imag, e12Imag, e20Imag);

        //Perform Delaunay Triangulation
        for (int i = 0; i < verts.Count; i++)
        {
            Vertex v = verts[i];

            Triangle containingTri = treeRoot.FindContainingTriangle(v.p);

            //Check for potential degenerate case when point lies on edge of triangle
            HalfEdge   e01 = containingTri.edge;
            HalfEdge   e12 = e01.next;
            HalfEdge   e20 = e12.next;
            Vector3    uvw = Geometry.ToBarycentricCoordinates(e01.origin.p, e12.origin.p, e20.origin.p, v.p);
            HalfEdge[] edges;
            if (uvw[0] < VecMath.epsilon)
            {
                edges = e12.InsertVertex(v);
            }
            else if (uvw[1] < VecMath.epsilon)
            {
                edges = e20.InsertVertex(v);
            }
            else if (uvw[2] < VecMath.epsilon)
            {
                edges = e01.InsertVertex(v);
            }
            else
            {
                edges = containingTri.InsertVertex(v);
            }

            //Flip triangles that don't satisfy delaunay property
            HashSet <HalfEdge>    sptSet   = new HashSet <HalfEdge>();
            LinkedList <HalfEdge> frontier = new LinkedList <HalfEdge>();
            foreach (HalfEdge edge in edges)
            {
                frontier.AddLast(edge);
            }

            LinkedListNode <HalfEdge> curNode;
            int t = 0;
            while (frontier.Count > 0 && t < 100)
            {
                t      += 1;
                curNode = frontier.First;
                frontier.RemoveFirst();
                HalfEdge ab = curNode.Value;
                HalfEdge ba = ab.twin;
                if (!sptSet.Contains(ab) && ba != null)
                {
                    HalfEdge bc = ab.next;
                    HalfEdge ca = bc.next;

                    HalfEdge ad = ba.next;
                    HalfEdge db = ad.next;
                    //Check if Delaunay Property is violated
                    if (Geometry.IsInCircumscribedCircle(ab.origin.p, bc.origin.p, ca.origin.p, db.origin.p))
                    {
                        //Flip triangle
                        HalfEdge dc = new HalfEdge(db.origin);
                        HalfEdge cd = new HalfEdge(ca.origin);
                        HalfEdge.SetTwins(dc, cd);
                        Triangle adc = new Triangle(ad, dc, ca);
                        Triangle bcd = new Triangle(bc, cd, db);

                        //Remove old edges from vertices
                        ab.origin.RemoveOutgoingEdge(ab);
                        ba.origin.RemoveOutgoingEdge(ba);
                        //Add new edges from flip to vertices
                        cd.origin.AddOutgoingEdge(cd);
                        dc.origin.AddOutgoingEdge(dc);

                        ab.incidentTriangle.children = new List <Triangle> {
                            adc, bcd
                        };
                        ba.incidentTriangle.children = new List <Triangle> {
                            adc, bcd
                        };

                        frontier.AddLast(ad);
                        frontier.AddLast(db);
                    }
                    sptSet.Add(ab);
                    sptSet.Add(ba);
                }
            }
        }

        //Insert constrained edges
        foreach (ConstrainedVertex[] segments in constrainedVerts)
        {
            HashSet <HalfEdge> holeBounds = new HashSet <HalfEdge>();
            HalfEdge           holeEdge   = null;
            bool isHole = segments.Length > 2;
            int  n      = isHole ? segments.Length : segments.Length - 1;
            for (int i = 0; i < n; i++)
            {
                Vertex v1 = segments[i % segments.Length];
                Vertex v2 = segments[(i + 1) % segments.Length];
                Debug.DrawLine(v1.p, v2.p, Color.magenta, 5.0f, false);

                Vector2  dir    = v2.p - v1.p;
                HalfEdge eStart = v1.GetOutgoingEdgeClockwiseFrom(dir);
                HalfEdge eEnd   = v2.GetOutgoingEdgeClockwiseFrom(-dir);

                List <HalfEdge> edgePortals = new List <HalfEdge>();
                HalfEdge        intersected = eStart.next.twin;
                Vertex          v           = v1;
                int             t           = 0;
                while (v != v2 && t < 100)
                {
                    t += 1;
                    edgePortals.Add(intersected);
                    v = intersected.prev.origin;
                    Vector2 newDir = v.p - v1.p;
                    if (VecMath.Det(dir, newDir) >= 0)
                    {
                        intersected = intersected.next.twin;
                    }
                    else
                    {
                        intersected = intersected.prev.twin;
                    }
                }


                List <HalfEdge> forwardEdgePortals = new List <HalfEdge>();
                forwardEdgePortals.Add(eStart);
                for (int j = 0; j < edgePortals.Count; j++)
                {
                    forwardEdgePortals.Add(edgePortals[j]);
                }

                List <HalfEdge> backwardEdgePortals = new List <HalfEdge>();
                backwardEdgePortals.Add(eEnd);
                for (int j = edgePortals.Count - 1; j >= 0; j--)
                {
                    backwardEdgePortals.Add(edgePortals[j].twin);
                }

                int      sL            = 0;
                HalfEdge eConstrainedL = PolygonTriangulation(ref sL, forwardEdgePortals);
                holeBounds.Add(eConstrainedL);
                int      sR            = 0;
                HalfEdge eConstrainedR = PolygonTriangulation(ref sR, backwardEdgePortals);
                holeEdge = eConstrainedR;
                HalfEdge.SetTwins(eConstrainedL, eConstrainedR);


                foreach (HalfEdge e in edgePortals)
                {
                    Vector2 ep1 = e.origin.p;
                    Vector2 ep2 = e.next.origin.p;
                    Debug.DrawLine(ep1, ep2, Color.green, 5.0f, false);
                }

                //v1.DrawOutgoingEdges();
                //Debug.DrawLine(eStart.origin.p, eStart.next.origin.p, Color.red, 5.0f, false);
            }

            if (isHole && holeEdge != null)
            {
                //We have a hole. Hide all triangles that are in hole
                CreateHole(holeEdge, holeBounds);
            }
        }

        //Generate Triangle list
        List <Triangle>    leafs   = new List <Triangle>();
        HashSet <Triangle> visited = new HashSet <Triangle>();

        int count = 0;

        treeRoot.GetRealLeafs(ref leafs, ref visited, ref count, 0);
        Debug.Log(count);

        foreach (Triangle leaf in leafs)
        {
            Vector3 p0 = leaf.edge.origin.p;
            Vector3 p1 = leaf.edge.next.origin.p;
            Vector3 p2 = leaf.edge.next.next.origin.p;
            Debug.DrawLine(p0, p1, Color.cyan, 5.0f, false);
            Debug.DrawLine(p1, p2, Color.cyan, 5.0f, false);
            Debug.DrawLine(p2, p0, Color.cyan, 5.0f, false);
        }

        return(leafs.ToArray());
    }