internal VEdge(VPoint start, FortuneSite left, FortuneSite right) { Start = start; Left = left; Right = right; //for bounding box edges if (left == null || right == null) { return; } //from negative reciprocal of slope of line from left to right //ala m = (left.y -right.y / left.x - right.x) SlopeRise = left.X - right.X; SlopeRun = -(left.Y - right.Y); Intercept = null; if (SlopeRise.ApproxEqual(0) || SlopeRun.ApproxEqual(0)) { return; } Slope = SlopeRise / SlopeRun; Intercept = start.Y - Slope * start.X; }
private int SortCornersClockwise(VPoint a, VPoint b) { // based on: https://social.msdn.microsoft.com/Forums/en-US/c4c0ce02-bbd0-46e7-aaa0-df85a3408c61/sorting-list-of-xy-coordinates-clockwise-sort-works-if-list-is-unsorted-but-fails-if-list-is?forum=csharplanguage // comparer to sort the array based on the points relative position to the center var atanA = Math.Atan2(a.Y - Y, a.X - X); var atanB = Math.Atan2(b.Y - Y, b.X - X); return(atanA < atanB ? -1 : (atanA > atanB ? 1 : 0)); }
public int SortCornersClockwise(VPoint A, VPoint B) { // based on: https://social.msdn.microsoft.com/Forums/en-US/c4c0ce02-bbd0-46e7-aaa0-df85a3408c61/sorting-list-of-xy-coordinates-clockwise-sort-works-if-list-is-unsorted-but-fails-if-list-is?forum=csharplanguage // comparer to sort the array based on the points relative position to the center var atanA = Math.Atan2(A.Y - Y, A.X - X); var atanB = Math.Atan2(B.Y - Y, B.X - X); if (atanA < atanB) { return(-1); } else if (atanA > atanB) { return(1); } return(0); }
public bool Contains(VPoint testPoint) { // helper method to determine if a point is inside the cell // based on meowNET's answer from: https://stackoverflow.com/questions/4243042/c-sharp-point-in-polygon bool result = false; int j = Points.Count - 1; for (int i = 0; i < Points.Count; i++) { if (Points[i].Y < testPoint.Y && Points[j].Y >= testPoint.Y || Points[j].Y < testPoint.Y && Points[i].Y >= testPoint.Y) { if (Points[i].X + ((testPoint.Y - Points[i].Y) / (Points[j].Y - Points[i].Y) * (Points[j].X - Points[i].X)) < testPoint.X) { result = !result; } } j = i; } return(result); }
internal void AddBeachSection(FortuneSiteEvent siteEvent, MinHeap <FortuneEvent> eventQueue, HashSet <FortuneCircleEvent> deleted, LinkedList <VEdge> edges) { var site = siteEvent.Site; var x = site.X; var directrix = site.Y; RBTreeNode <BeachSection> leftSection = null; RBTreeNode <BeachSection> rightSection = null; var node = beachLine.Root; //find the parabola(s) above this site while (node != null && leftSection == null && rightSection == null) { var distanceLeft = LeftBreakpoint(node, directrix) - x; if (distanceLeft > 0) { //the new site is before the left breakpoint if (node.Left == null) { rightSection = node; } else { node = node.Left; } continue; } var distanceRight = x - RightBreakpoint(node, directrix); if (distanceRight > 0) { //the new site is after the right breakpoint if (node.Right == null) { leftSection = node; } else { node = node.Right; } continue; } //the point lies below the left breakpoint if (distanceLeft.ApproxEqual(0)) { leftSection = node.Previous; rightSection = node; continue; } //the point lies below the right breakpoint if (distanceRight.ApproxEqual(0)) { leftSection = node; rightSection = node.Next; continue; } // distance Right < 0 and distance Left < 0 // this section is above the new site leftSection = rightSection = node; } //our goal is to insert the new node between the //left and right sections var section = new BeachSection(site); //left section could be null, in which case this node is the first //in the tree var newSection = beachLine.InsertSuccessor(leftSection, section); //new beach section is the first beach section to be added if (leftSection == null && rightSection == null) { return; } //main case: //if both left section and right section point to the same valid arc //we need to split the arc into a left arc and a right arc with our //new arc sitting in the middle if (leftSection != null && leftSection == rightSection) { //if the arc has a circle event, it was a false alarm. //remove it if (leftSection.Data.CircleEvent != null) { deleted.Add(leftSection.Data.CircleEvent); leftSection.Data.CircleEvent = null; } //we leave the existing arc as the left section in the tree //however we need to insert the right section defined by the arc var copy = new BeachSection(leftSection.Data.Site); rightSection = beachLine.InsertSuccessor(newSection, copy); //grab the projection of this site onto the parabola var y = ParabolaMath.EvalParabola(leftSection.Data.Site.X, leftSection.Data.Site.Y, directrix, x); var intersection = new VPoint(x, y); //create the two half edges corresponding to this intersection var leftEdge = new VEdge(intersection, site, leftSection.Data.Site); var rightEdge = new VEdge(intersection, leftSection.Data.Site, site); leftEdge.Neighbor = rightEdge; //put the edge in the list edges.AddFirst(leftEdge); //store the left edge on each arc section newSection.Data.Edge = leftEdge; rightSection.Data.Edge = rightEdge; //store neighbors for delaunay leftSection.Data.Site.Neighbors.Add(newSection.Data.Site); newSection.Data.Site.Neighbors.Add(leftSection.Data.Site); //create circle events CheckCircle(leftSection, eventQueue); CheckCircle(rightSection, eventQueue); } //site is the last beach section on the beach line //this can only happen if all previous sites //had the same y value else if (leftSection != null && rightSection == null) { var start = new VPoint((leftSection.Data.Site.X + site.X) / 2, float.MinValue); var infEdge = new VEdge(start, leftSection.Data.Site, site); var newEdge = new VEdge(start, site, leftSection.Data.Site); newEdge.Neighbor = infEdge; edges.AddFirst(newEdge); leftSection.Data.Site.Neighbors.Add(newSection.Data.Site); newSection.Data.Site.Neighbors.Add(leftSection.Data.Site); newSection.Data.Edge = newEdge; //cant check circles since they are colinear } //site is directly above a break point else if (leftSection != null && leftSection != rightSection) { //remove false alarms if (leftSection.Data.CircleEvent != null) { deleted.Add(leftSection.Data.CircleEvent); leftSection.Data.CircleEvent = null; } if (rightSection.Data.CircleEvent != null) { deleted.Add(rightSection.Data.CircleEvent); rightSection.Data.CircleEvent = null; } //the breakpoint will dissapear if we add this site //which means we will create an edge //we treat this very similar to a circle event since //an edge is finishing at the center of the circle //created by circumscribing the left center and right //sites //bring a to the origin var leftSite = leftSection.Data.Site; var ax = leftSite.X; var ay = leftSite.Y; var bx = site.X - ax; var by = site.Y - ay; var rightSite = rightSection.Data.Site; var cx = rightSite.X - ax; var cy = rightSite.Y - ay; var d = bx * cy - by * cx; var magnitudeB = bx * bx + by * by; var magnitudeC = cx * cx + cy * cy; var vertex = new VPoint( (cy * magnitudeB - by * magnitudeC) / (2 * d) + ax, (bx * magnitudeC - cx * magnitudeB) / (2 * d) + ay); rightSection.Data.Edge.End = vertex; //next we create a two new edges newSection.Data.Edge = new VEdge(vertex, site, leftSection.Data.Site); rightSection.Data.Edge = new VEdge(vertex, rightSection.Data.Site, site); edges.AddFirst(newSection.Data.Edge); edges.AddFirst(rightSection.Data.Edge); //add neighbors for delaunay newSection.Data.Site.Neighbors.Add(leftSection.Data.Site); leftSection.Data.Site.Neighbors.Add(newSection.Data.Site); newSection.Data.Site.Neighbors.Add(rightSection.Data.Site); rightSection.Data.Site.Neighbors.Add(newSection.Data.Site); CheckCircle(leftSection, eventQueue); CheckCircle(rightSection, eventQueue); } }
internal void RemoveBeachSection(FortuneCircleEvent circle, MinHeap <FortuneEvent> eventQueue, HashSet <FortuneCircleEvent> deleted, LinkedList <VEdge> edges) { var section = circle.ToDelete; var x = circle.X; var y = circle.YCenter; var vertex = new VPoint(x, y); //multiple edges could end here var toBeRemoved = new List <RBTreeNode <BeachSection> >(); //look left var prev = section.Previous; while (prev.Data.CircleEvent != null && (x - prev.Data.CircleEvent.X).ApproxEqual(0) && (y - prev.Data.CircleEvent.Y).ApproxEqual(0)) { toBeRemoved.Add(prev); prev = prev.Previous; } var next = section.Next; while (next.Data.CircleEvent != null && (x - next.Data.CircleEvent.X).ApproxEqual(0) && (y - next.Data.CircleEvent.Y).ApproxEqual(0)) { toBeRemoved.Add(next); next = next.Next; } section.Data.Edge.End = vertex; section.Next.Data.Edge.End = vertex; section.Data.CircleEvent = null; //odds are this float writes a few edges but this is clean... foreach (var remove in toBeRemoved) { remove.Data.Edge.End = vertex; remove.Next.Data.Edge.End = vertex; deleted.Add(remove.Data.CircleEvent); remove.Data.CircleEvent = null; } //need to delete all upcoming circle events with this node if (prev.Data.CircleEvent != null) { deleted.Add(prev.Data.CircleEvent); prev.Data.CircleEvent = null; } if (next.Data.CircleEvent != null) { deleted.Add(next.Data.CircleEvent); next.Data.CircleEvent = null; } //create a new edge with start point at the vertex and assign it to next var newEdge = new VEdge(vertex, next.Data.Site, prev.Data.Site); next.Data.Edge = newEdge; edges.AddFirst(newEdge); //add neighbors for delaunay prev.Data.Site.Neighbors.Add(next.Data.Site); next.Data.Site.Neighbors.Add(prev.Data.Site); //remove the sectionfrom the tree beachLine.RemoveNode(section); foreach (var remove in toBeRemoved) { beachLine.RemoveNode(remove); } CheckCircle(prev, eventQueue); CheckCircle(next, eventQueue); }