/// <summary>
        /// Non-allocating version of CalculateDiagram.
        ///
        /// I guess it's not strictly true that it generates NO garbage, because
        /// it might if it has to resize internal buffers, but all buffers are
        /// reused from invocation to invocation.
        /// </summary>
        public void CalculateDiagram(IList <Vector2> inputVertices, ref VoronoiDiagram result)
        {
            // TODO: special case for 1 points
            // TODO: special case for 2 points
            // TODO: special case for 3 points
            // TODO: special case for collinear points
            if (inputVertices.Count < 3)
            {
                throw new NotImplementedException("Not implemented for < 3 vertices");
            }

            if (result == null)
            {
                result = new VoronoiDiagram();
            }

            var trig = result.Triangulation;

            result.Clear();

            Profiler.BeginSample("Delaunay triangulation");
            delCalc.CalculateTriangulation(inputVertices, ref trig);
            Profiler.EndSample();

            pts.Clear();

            var verts   = trig.Vertices;
            var tris    = trig.Triangles;
            var centers = result.Vertices;
            var edges   = result.Edges;


            if (tris.Count > pts.Capacity)
            {
                   {
                    pts.Capacity = tris.Count;
                }
            }
            if (tris.Count > edges.Capacity)
            {
                edges.Capacity = tris.Count;
            }


            for (int ti = 0; ti < tris.Count; ti += 3)
            {
                var p0 = verts[tris[ti]];
                var p1 = verts[tris[ti + 1]];
                var p2 = verts[tris[ti + 2]];

                // Triangle is in CCW order
                Debug.Assert(Geom.ToTheLeft(p2, p0, p1));

                centers.Add(Geom.CircumcircleCenter(p0, p1, p2));
            }


            for (int ti = 0; ti < tris.Count; ti += 3)
            {
                pts.Add(new PointTriangle(tris[ti], ti));
                pts.Add(new PointTriangle(tris[ti + 1], ti));
                pts.Add(new PointTriangle(tris[ti + 2], ti));
            }

            cmp.tris  = tris;
            cmp.verts = verts;

            Profiler.BeginSample("Sorting");
            pts.Sort(cmp);
            Profiler.EndSample();

            // The comparer lives on between runs of the algorithm, so clear the
            // reference to the arrays so that the reference is lost. It may be
            // the case that the calculator lives on much longer than the
            // results, and not clearing these would keep the results alive,
            // leaking memory.
            cmp.tris  = null;
            cmp.verts = null;

            for (int i = 0; i < pts.Count; i++)
            {
                result.FirstEdgeBySite.Add(edges.Count);

                var start = i;
                var end   = -1;

                for (int j = i + 1; j < pts.Count; j++)
                {
                    if (pts[i].Point != pts[j].Point)
                    {
                        end = j - 1;
                        break;
                    }
                }

                if (end == -1)
                {
                    end = pts.Count - 1;
                }

                i = end;

                var count = end - start;

                Debug.Assert(count >= 0);

                for (int ptiCurr = start; ptiCurr <= end; ptiCurr++)
                {
                    bool isEdge;

                    var ptiNext = ptiCurr + 1;

                    if (ptiNext > end)
                    {
                        ptiNext = start;
                    }

                    var ptCurr = pts[ptiCurr];
                    var ptNext = pts[ptiNext];

                    var tiCurr = ptCurr.Triangle;
                    var tiNext = ptNext.Triangle;

                    var p0 = verts[ptCurr.Point];

                    var v2nan = new Vector2(float.NaN, float.NaN);

                    if (count == 0)
                    {
                        isEdge = true;
                    }
                    else if (count == 1)
                    {
                        var cCurr = Geom.TriangleCentroid(verts[tris[tiCurr]], verts[tris[tiCurr + 1]], verts[tris[tiCurr + 2]]);
                        var cNext = Geom.TriangleCentroid(verts[tris[tiNext]], verts[tris[tiNext + 1]], verts[tris[tiNext + 2]]);

                        isEdge = Geom.ToTheLeft(cCurr, p0, cNext);
                    }
                    else
                    {
                        isEdge = !SharesEdge(tris, tiCurr, tiNext);
                    }

                    if (isEdge)
                    {
                        Vector2 v0, v1;

                        if (ptCurr.Point == tris[tiCurr])
                        {
                            v0 = verts[tris[tiCurr + 2]] - verts[tris[tiCurr + 0]];
                        }
                        else if (ptCurr.Point == tris[tiCurr + 1])
                        {
                            v0 = verts[tris[tiCurr + 0]] - verts[tris[tiCurr + 1]];
                        }
                        else
                        {
                            Debug.Assert(ptCurr.Point == tris[tiCurr + 2]);
                            v0 = verts[tris[tiCurr + 1]] - verts[tris[tiCurr + 2]];
                        }

                        if (ptNext.Point == tris[tiNext])
                        {
                            v1 = verts[tris[tiNext + 0]] - verts[tris[tiNext + 1]];
                        }
                        else if (ptNext.Point == tris[tiNext + 1])
                        {
                            v1 = verts[tris[tiNext + 1]] - verts[tris[tiNext + 2]];
                        }
                        else
                        {
                            Debug.Assert(ptNext.Point == tris[tiNext + 2]);
                            v1 = verts[tris[tiNext + 2]] - verts[tris[tiNext + 0]];
                        }

                        edges.Add(new VoronoiDiagram.Edge(
                                      VoronoiDiagram.EdgeType.RayCCW,
                                      ptCurr.Point,
                                      tiCurr / 3,
                                      -1,
                                      Geom.RotateRightAngle(v0)
                                      ));

                        edges.Add(new VoronoiDiagram.Edge(
                                      VoronoiDiagram.EdgeType.RayCW,
                                      ptCurr.Point,
                                      tiNext / 3,
                                      -1,
                                      Geom.RotateRightAngle(v1)
                                      ));
                    }
                    else
                    {
                        if (!Geom.AreCoincident(centers[tiCurr / 3], centers[tiNext / 3]))
                        {
                            edges.Add(new VoronoiDiagram.Edge(
                                          VoronoiDiagram.EdgeType.Segment,
                                          ptCurr.Point,
                                          tiCurr / 3,
                                          tiNext / 3,
                                          v2nan
                                          ));
                        }
                    }
                }
            }
        }