/// <summary> /// Is the point to the left of the edge? /// </summary> bool ToTheLeft(int pi, int li0, int li1) { if (li0 == -2) { return(Higher(li1, pi)); } else if (li0 == -1) { return(Higher(pi, li1)); } else if (li1 == -2) { return(Higher(pi, li0)); } else if (li1 == -1) { return(Higher(li0, pi)); } else { Debug.Assert(li0 >= 0); Debug.Assert(li1 >= 0); return(Geom.ToTheLeft(verts[pi], verts[li0], verts[li1])); } }
/// <summary> /// Is the edge legal, or does it need to be flipped? /// </summary> bool LegalEdge(int k, int l, int i, int j) { Debug.Assert(k != highest && k >= 0); var lMagic = l < 0; var iMagic = i < 0; var jMagic = j < 0; Debug.Assert(!(iMagic && jMagic)); if (lMagic) { return(true); } else if (iMagic) { Debug.Assert(!jMagic); var p = verts[l]; var l0 = verts[k]; var l1 = verts[j]; return(Geom.ToTheLeft(p, l0, l1)); } else if (jMagic) { Debug.Assert(!iMagic); var p = verts[l]; var l0 = verts[k]; var l1 = verts[i]; return(!Geom.ToTheLeft(p, l0, l1)); } else { Debug.Assert(k >= 0 && l >= 0 && i >= 0 && j >= 0); var p = verts[l]; var c0 = verts[k]; var c1 = verts[i]; var c2 = verts[j]; Debug.Assert(Geom.ToTheLeft(c2, c0, c1)); Debug.Assert(Geom.ToTheLeft(c2, c1, p)); return(!Geom.InsideCircumcircle(p, c0, c1, c2)); } }
/// <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 )); } } } } }
/// <summary> /// Clip site of voronoi diagram using polygon (must be convex), /// returning the clipped vertices in clipped list. Modifies neither /// polygon nor diagram, so can be run in parallel for several sites at /// once. /// </summary> public void ClipSite(VoronoiDiagram diag, IList <Vector2> polygon, int site, ref List <Vector2> clipped) { pointsIn.Clear(); pointsIn.AddRange(polygon); int firstEdge, lastEdge; if (site == diag.Sites.Count - 1) { firstEdge = diag.FirstEdgeBySite[site]; lastEdge = diag.Edges.Count - 1; } else { firstEdge = diag.FirstEdgeBySite[site]; lastEdge = diag.FirstEdgeBySite[site + 1] - 1; } for (int ei = firstEdge; ei <= lastEdge; ei++) { pointsOut.Clear(); var edge = diag.Edges[ei]; Vector2 lp, ld; if (edge.Type == VoronoiDiagram.EdgeType.RayCCW || edge.Type == VoronoiDiagram.EdgeType.RayCW) { lp = diag.Vertices[edge.Vert0]; ld = edge.Direction; if (edge.Type == VoronoiDiagram.EdgeType.RayCW) { ld *= -1; } } else if (edge.Type == VoronoiDiagram.EdgeType.Segment) { var lp0 = diag.Vertices[edge.Vert0]; var lp1 = diag.Vertices[edge.Vert1]; lp = lp0; ld = lp1 - lp0; } else if (edge.Type == VoronoiDiagram.EdgeType.Line) { throw new NotSupportedException("Haven't implemented voronoi halfplanes yet"); } else { Debug.Assert(false); return; } for (int pi0 = 0; pi0 < pointsIn.Count; pi0++) { var pi1 = pi0 == pointsIn.Count - 1 ? 0 : pi0 + 1; var p0 = pointsIn[pi0]; var p1 = pointsIn[pi1]; var p0Inside = Geom.ToTheLeft(p0, lp, lp + ld); var p1Inside = Geom.ToTheLeft(p1, lp, lp + ld); if (p0Inside && p1Inside) { pointsOut.Add(p1); } else if (!p0Inside && !p1Inside) { // Do nothing, both are outside } else { var intersection = Geom.LineLineIntersection(lp, ld.normalized, p0, (p1 - p0).normalized); if (p0Inside) { pointsOut.Add(intersection); } else if (p1Inside) { pointsOut.Add(intersection); pointsOut.Add(p1); } else { Debug.Assert(false); } } } var tmp = pointsIn; pointsIn = pointsOut; pointsOut = tmp; } if (clipped == null) { clipped = new List <Vector2>(); } else { clipped.Clear(); } clipped.AddRange(pointsIn); /* * pointsIn.Clear(); * * pointsIn.AddRange(polygon); * * int firstEdge, lastEdge; * * if (site == diag.Sites.Count - 1) * { * firstEdge = diag.FirstEdgeBySite[site]; * lastEdge = diag.Edges.Count - 1; * } * else * { * firstEdge = diag.FirstEdgeBySite[site]; * lastEdge = diag.FirstEdgeBySite[site + 1] - 1; * } * * for (int ei = firstEdge; ei <= lastEdge; ei++) * { * pointsOut.Clear(); * * var edge = diag.Edges[ei]; * * Vector2 lp, ld; * * if (edge.Type == VoronoiDiagram.EdgeType.RayCCW || edge.Type == VoronoiDiagram.EdgeType.RayCW) * { * lp = diag.Vertices[edge.Vert0]; * ld = edge.Direction; * * if (edge.Type == VoronoiDiagram.EdgeType.RayCW) * { * ld *= -1; * } * } * else if (edge.Type == VoronoiDiagram.EdgeType.Segment) * { * var lp0 = diag.Vertices[edge.Vert0]; * var lp1 = diag.Vertices[edge.Vert1]; * * lp = lp0; * ld = lp1 - lp0; * } * else if (edge.Type == VoronoiDiagram.EdgeType.Line) * { * throw new NotSupportedException("Haven't implemented voronoi halfplanes yet"); * } * else * { * Debug.Assert(false); * return; * } * * for (int pi0 = 0; pi0 < pointsIn.Count; pi0++) * { * var pi1 = pi0 == pointsIn.Count - 1 ? 0 : pi0 + 1; * * var p0 = pointsIn[pi0]; * var p1 = pointsIn[pi1]; * * var p0Inside = Geom.ToTheLeft(p0, lp, lp + ld); * var p1Inside = Geom.ToTheLeft(p1, lp, lp + ld); * * if (p0Inside && p1Inside) * { * pointsOut.Add(p1); * } * else if (!p0Inside && !p1Inside) * { * // Do nothing, both are outside * } * else * { * var intersection = Geom.LineLineIntersection(lp, ld.normalized, p0, (p1 - p0).normalized); * * if (p0Inside) * { * pointsOut.Add(intersection); * } * else if (p1Inside) * { * pointsOut.Add(intersection); * pointsOut.Add(p1); * } * else * { * Debug.Assert(false); * } * } * } * * if(pointsOut.Count == 0) * { * //pointsOut = new List<Vector2>(pointsIn); * } * * var tmp = pointsIn; * pointsIn = pointsOut; * pointsOut = tmp; * } * * if (clipped == null) * { * clipped = new List<Vector2>(); * } * else * { * clipped.Clear(); * } * * clipped.AddRange(pointsIn); * */ }