/// <summary> /// Gets the arc on the beach line at the x coordinate (i.e. right above the new site event) /// </summary> /// <param name="p"></param> /// <returns></returns> private JCVHalfEdge GetEdgeAboveX(PointF p) { //guess that it's close by to last insert JCVHalfEdge he = lastInserted; if (he == null) { //changed to add "+ Bounds.Right"; believe original expression was erroneous he = (p.X < Bounds.Width / 2 + Bounds.Right) ? beachlineStart : beachlineEnd; } //note: could be optimized with binary search //then search right or left (as appropriate) until found if (he == beachlineStart || (he != beachlineEnd && RightOf(he, p))) { do { he = he.right; }while (he != beachlineEnd && RightOf(he, p)); he = he.left; } else { do { he = he.left; }while (he != beachlineStart && !RightOf(he, p)); } return(he); }
private void SiteEvent(JCVSite site) { JCVHalfEdge left = GetEdgeAboveX(site.center); JCVHalfEdge right = left.right; JCVSite bottom = (left.Edge is null) ? sites[0] : left.rSite; JCVEdge edge = new JCVEdge(bottom, site); edges.Add(edge); JCVHalfEdge he1 = new JCVHalfEdge(edge, false, left); JCVHalfEdge he2 = new JCVHalfEdge(edge, true, he1); lastInserted = right; PointF p; if (CheckCircleEvent(left, he1, out p)) { priorityQueue.Remove(left); left.vertex = p; left.Y = p.Y + PointDistance(site.center, p); priorityQueue.Add(left); } if (CheckCircleEvent(he2, right, out p)) { he2.vertex = p; he2.Y = p.Y + PointDistance(site.center, p); priorityQueue.Add(he2); } }
//note to self: this is currently a black box. Figure out how it works at some point private bool RightOf(JCVHalfEdge he, PointF p) { JCVEdge edge = he.Edge; JCVSite topsite = edge.Sites[1]; bool rightOfSite = (p.X > topsite.X); if (rightOfSite ^ he.directionIsRight) //direction and rightOfSite don't match { return(rightOfSite); } float dxp, dyp, dxs, t1, t2, t3, yl; bool above; if (edge.A == 1) { dyp = p.Y - topsite.Y; dxp = p.X - topsite.X; bool fast = false; if ((!rightOfSite & (edge.B < 0)) || (rightOfSite & (edge.B >= 0))) { above = dyp >= edge.B * dxp; fast = above; } else { above = (p.X + p.Y * edge.B) > edge.C; if (edge.B < 0) { above = !above; } if (!above) { fast = true; } } if (!fast) { dxs = topsite.X - edge.Sites[0].X; above = edge.B * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + edge.B * edge.B); if (edge.B < 0) { above = !above; } } } else // edge.b == 1 { yl = edge.C - edge.A * p.X; t1 = p.Y - yl; t2 = p.X - topsite.X; t3 = yl - topsite.Y; above = t1 * t1 > (t2 * t2 + t3 * t3); } return(he.directionIsRight ^ above); }
private void CircleEvent() { JCVHalfEdge left = priorityQueue.First(); priorityQueue.Remove(left); JCVHalfEdge leftleft = left.left; JCVHalfEdge right = left.right; JCVHalfEdge rightright = right.right; JCVSite bottom = left.lSite; JCVSite top = right.rSite; PointF vertex = left.vertex; EndPoints(left.Edge, vertex, left.directionIsRight); EndPoints(right.Edge, vertex, right.directionIsRight); lastInserted = rightright; priorityQueue.Remove(right); left.Unlink(); right.Unlink(); bool dirIsRight = false; if (bottom.Y > top.Y) { JCVSite temp = bottom; bottom = top; top = temp; dirIsRight = true; } JCVEdge edge = new JCVEdge(bottom, top); edges.Add(edge); JCVHalfEdge he = new JCVHalfEdge(edge, dirIsRight, leftleft); EndPoints(edge, vertex, !dirIsRight); PointF p; if (CheckCircleEvent(leftleft, he, out p)) { priorityQueue.Remove(leftleft); leftleft.vertex = p; leftleft.Y = p.Y + PointDistance(bottom.center, p); priorityQueue.Add(leftleft); } if (CheckCircleEvent(he, rightright, out p)) { he.vertex = p; he.Y = p.Y + PointDistance(bottom.center, p); priorityQueue.Add(he); } }
private bool CheckCircleEvent(JCVHalfEdge he1, JCVHalfEdge he2, out PointF vertex) { JCVEdge e1 = he1.Edge; JCVEdge e2 = he2.Edge; vertex = new PointF(); if (e1 is null || e2 is null || e1.Sites[1] == e2.Sites[1]) { return(false); } return(HalfEdgeIntersection(he1, he2, out vertex)); }
private bool HalfEdgeIntersection(JCVHalfEdge he1, JCVHalfEdge he2, out PointF intersect) { JCVEdge e1 = he1.Edge; JCVEdge e2 = he2.Edge; intersect = new PointF(); float d = e1.A * e2.B - e1.B * e2.A; //determinant if (-EDGE_INTERSECT_THRESHOLD < d && d < EDGE_INTERSECT_THRESHOLD) // lines are parallel { return(false); } float xint = (e1.C * e2.B - e1.B * e2.C) / d; float yint = (e1.A * e2.C - e1.C * e2.A) / d; intersect = new PointF(xint, yint); JCVEdge e; JCVHalfEdge he; if (IsPointBefore(e1.Sites[1].center, e2.Sites[1].center)) { he = he1; e = e1; } else { he = he2; e = e2; } bool rightOfSite = intersect.X >= e.Sites[1].X; if (rightOfSite ^ he.directionIsRight) { return(false); } return(true); }
/// <summary> /// Generate the voronoi diagram. /// </summary> /// <param name="points"></param> /// <param name="rect"></param> /// <param name="clipper"></param> public void GenerateDiagram(ref List <PointF> points, RectangleF rect, JCVClipper userClipper) { beachlineStart = new JCVHalfEdge(null, false); beachlineEnd = new JCVHalfEdge(null, false); clipper = userClipper; priorityQueue = new SortedSet <JCVHalfEdge>(); beachlineStart.right = beachlineEnd; beachlineEnd.left = beachlineStart; lastInserted = null; //maybe not needed; added for clarity edges = new List <JCVEdge>(); //create sites for each distinct point, sorted by min(y),min(x) sites = points.Distinct().Select((p, index) => new JCVSite(p, index)).OrderBy(s => s.Y).ThenBy(s => s.X).ToList(); bool noRect = (rect == default); if (noRect) { rect.X = sites.Min(s => s.X); rect.Y = sites.Min(s => s.Y); rect.Width = sites.Max(s => s.X) - rect.X; rect.Height = sites.Max(s => s.Y) - rect.Y; } //uses clipper to clip any points outside bounds. clipper.boundingBox = rect; sites = (List <JCVSite>)sites.Where(s => clipper.TestPoint(s.center)).ToList(); if (noRect) { // JCash used ceil and floor to adjust the bounding box to int coords- necessary? // see: jcv_rect_round(&tmp_rect); // Will add later if needed //pad bounding box by 10 in all directions clipper.boundingBox.Inflate(10, 10); rect = clipper.boundingBox; } this.Bounds = rect; int siteIndex = 1; //sites[0] is saved for initial bottom site int sCount = sites.Count; bool finished = false; while (!finished) { bool pqEmpty = (priorityQueue.Count == 0); bool before = true; if (!pqEmpty && siteIndex < sCount) { JCVHalfEdge he = priorityQueue.ElementAt(0); PointF p = new PointF(he.vertex.X, he.Y); before = IsPointBefore(sites[siteIndex].center, p); } if (siteIndex < sCount && (pqEmpty || before)) { SiteEvent(sites[siteIndex++]); } else if (!pqEmpty) { CircleEvent(); } else { finished = true; } } //end while for (JCVHalfEdge temphe = beachlineStart.right; temphe != beachlineEnd; temphe = temphe.right) { FinishLine(temphe.Edge); } FillGaps(); }