/* Function: findRightBreakPoint * ---------------------------- * Finds the breakpoint to the right of the new arc in the beach line. */ private float findRightBreakPoint(BeachArc node, float sweepLineY) { BeachArc rightArc = node.Next; if (rightArc) { return(findLeftBreakPoint(rightArc, sweepLineY)); } return((node.site.y == sweepLineY) ? node.site.x : Mathf.Infinity); }
/* Function: deleteCircleEvent * --------------------------- * Deletes a potential circle event associated with a site event * from the event queue (it is no longer correct). */ private void deleteCircleEvent(BeachArc arc) { CircleEvent ce = arc.circleEvent; arc.circleEvent = null; if (ce != null && sweepLine.Contains(ce)) { sweepLine.Remove(ce); usedCircleEvents.Add(ce); } }
/* Function: findLeftBreakPoint * ---------------------------- * Finds the breakpoint to the left of the new arc in the beach line. */ private float findLeftBreakPoint(BeachArc node, float sweepLineY) { // Right site Site site = node.site; float rightFocusX = site.x; float rightFocusY = site.y; float rightFocusSweepDiff = rightFocusY - sweepLineY; // Parabola focus on directrix (sweep line) if (rightFocusSweepDiff == 0.0f) { return(rightFocusX); } // New node, don't do anything BeachArc leftArc = node.Prev; if (leftArc == null) { return(-Mathf.Infinity); } // Left site site = leftArc.site; float leftFocusX = site.x; float leftFocusY = site.y; float leftFocusSweepDiff = leftFocusY - sweepLineY; // Parabola focus on directrix (sweep line) if (leftFocusSweepDiff == 0.0f) { return(leftFocusX); } // Find intersection float xFocusDiff = leftFocusX - rightFocusX; float inverseYDiff = 1 / rightFocusSweepDiff - 1 / leftFocusSweepDiff; float b = xFocusDiff / leftFocusSweepDiff; // Like quadratic equation centered at rightfocusX and different coefficients if (inverseYDiff != 0) { return((-b + Mathf.Sqrt(b * b - 2 * inverseYDiff * (xFocusDiff * xFocusDiff / (-2 * leftFocusSweepDiff) - leftFocusY + leftFocusSweepDiff / 2 + rightFocusY - rightFocusSweepDiff / 2))) / inverseYDiff + rightFocusX); } // Both parabolas are the same distance to sweep line, breakpoint is in middle return((rightFocusX + leftFocusX) / 2); }
/* Function: findNewArcPosition * ---------------------------- * Finds left and right components of arc above the current one to locate * where new site arc should fall and updates the values. */ private void findNewArcPosition(float siteX, float sweepLineY, ref BeachArc leftArc, ref BeachArc rightArc) { BeachArc node = beachLine.Root; float xLeft, xRight; while (node) { xLeft = findLeftBreakPoint(node, sweepLineY) - siteX; // Left node x-value falls somewhere on the left edge of the beachsection if (xLeft > EPS) { node = node.Left; } else { xRight = siteX - findRightBreakPoint(node, sweepLineY); // Right node x-value falls somewhere after the right edge of the beachsection if (xRight > EPS) { if (node.Right == null) { leftArc = node; break; } node = node.Right; // continue right } else { // Left node x-value falls exactly on the left edge of the beachsection if (xLeft > -EPS) { leftArc = node.Prev; rightArc = node; } // Right node x-value falls exactly on the right edge of the beachsection else if (xRight > -EPS) { leftArc = node; rightArc = node.Next; } // Node falls in the middle of the beachsection else { leftArc = rightArc = node; } break; } } } }
/* Method: reset * ------------- * Resets to compute a new diagram. */ public void reset() { // Leftover beach sections to the used list if (beachLine.Root) { BeachArc arc = beachLine.GetFirst(beachLine.Root); while (arc) { usedBeachArcs.Add(arc); arc = arc.Next; } } beachLine.Root = null; // Reset sweep line (should be empty) sweepLine.Clear(); }
/* Function: createNewBeachArc * --------------------------- * Creates new beach arc from provided site. Attempts to find * 'used' one to minimize memory costs. */ private BeachArc createNewBeachArc(Site site) { // See if used arc available for repurposing BeachArc arc = (usedBeachArcs.Count > 0) ? usedBeachArcs.Last() : null; if (arc) { usedBeachArcs.Remove(arc); arc.site = site; } // Otherwise create new one else { arc = new BeachArc(site); } return(arc); }
/* Function: getTripleArcCircleEvent * --------------------------------- * Checks sites of three arcs for convergence of breakpoints, i.e. beach will * collapse giving rise to a circle event. If true, gets a circle event and * updates its fields on the receiving arc and event queue. * * Note: Calculation of circle event based on Jeremie St-Amand's: * https://github.com/jesta88/Unity-Voronoi/blob/master/Assets/Voronoi/FortuneVoronoi.cs */ private void getTripleArcCircleEvent(Site left, ref BeachArc center, Site right) { float cx = center.site.x; float cy = center.site.y; float lx = left.x - cx; float ly = left.y - cy; float rx = right.x - cx; float ry = right.y - cy; float orientation = 2 * (lx * ry - ly * rx); // Points are CW, beach section does not collapse so no event if (orientation >= -2e-12) { return; } float lLengthSquared = lx * lx + ly * ly; float rLengthSquared = rx * rx + ry * ry; float x = (ry * lLengthSquared - ly * rLengthSquared) / orientation; float y = (lx * rLengthSquared - rx * lLengthSquared) / orientation; float yCenter = y + cy; // at or below sweepline // Get circle event to setup CircleEvent circleEvent = null; if (usedCircleEvents.Count > 0) { circleEvent = usedCircleEvents.Last(); usedCircleEvents.Remove(circleEvent); } else { circleEvent = new CircleEvent(); } // Update fields circleEvent.site = center.site; circleEvent.arc = center; circleEvent.setPosition(x + cx, yCenter + Mathf.Sqrt(x * x + y * y), yCenter); center.circleEvent = circleEvent; // Add to event queue sweepLine.Enqueue(circleEvent, circleEvent); }
/* Function: findPotentialCircleEvent * ---------------------------------- * Checks for a potential circle event after a site event and adds it * to the event queue and references it on the arc if found. Done by checking * if triple of consecutive arcs (including new one) have breakpoints that * converge. */ private void findPotentialCircleEvent(BeachArc arc) { BeachArc leftArc = arc.Prev; BeachArc rightArc = arc.Next; // Handle missing edge (shouldn't happen) if (leftArc == null || rightArc == null) { //Debug.LogError("Error attaching potential circle event, a surrounding arc is null."); return; } // No convergence, arc sides same if (leftArc.site == rightArc.site) { return; } // If left, center, right site locations are CW no event, else event returned getTripleArcCircleEvent(leftArc.site, ref arc, rightArc.site); }
/* Function: handleCircleEvent * --------------------------- * Handles circle event. */ private void handleCircleEvent(BeachArc arc) { CircleEvent circleEvent = arc.circleEvent; Vertex center = new Vertex(circleEvent.x, circleEvent.yCircleCenter); BeachArc prev = arc.Prev; BeachArc next = arc.Next; LinkedList <BeachArc> disappearingTransitions = new LinkedList <BeachArc>(); disappearingTransitions.AddLast(arc); deleteBeachArc(arc); // Handle collapse left BeachArc leftArc = prev; while (leftArc.circleEvent != null && Mathf.Abs(center.x - leftArc.circleEvent.x) < EPS && Mathf.Abs(center.y - leftArc.circleEvent.yCircleCenter) < EPS) { prev = leftArc.Prev; disappearingTransitions.AddFirst(leftArc); deleteBeachArc(leftArc); leftArc = prev; } // New left arc not dissappearing, but used in edge updates disappearingTransitions.AddFirst(leftArc); deleteCircleEvent(leftArc); // Hanlde collapse right BeachArc rightArc = next; while (rightArc.circleEvent != null && Mathf.Abs(center.x - rightArc.circleEvent.x) < EPS && Mathf.Abs(center.y - rightArc.circleEvent.yCircleCenter) < EPS) { next = rightArc.Next; disappearingTransitions.AddLast(rightArc); deleteBeachArc(rightArc); rightArc = next; } // New right arc not dissappearing, but used in edge updates disappearingTransitions.AddLast(rightArc); deleteCircleEvent(rightArc); // Link existing edges at start point int nArcs = disappearingTransitions.Count; for (int i = 1; i < nArcs; i++) { rightArc = disappearingTransitions.ElementAt(i); leftArc = disappearingTransitions.ElementAt(i - 1); rightArc.edge.setStartVertex(leftArc.site, rightArc.site, center); } // Create new edge between previously non-adjacent arcs // New vertex defines end point relative to site on the left leftArc = disappearingTransitions.ElementAt(0); rightArc = disappearingTransitions.ElementAt(nArcs - 1); rightArc.edge = createNewEdge(leftArc.site, rightArc.site, null, center); // Update circle events for arcs findPotentialCircleEvent(leftArc); findPotentialCircleEvent(rightArc); }
/* Function: deleteBeachArc * ------------------------ * Deletes an arc and associated circle event from the beach line. */ private void deleteBeachArc(BeachArc arc) { deleteCircleEvent(arc); beachLine.Remove(arc); usedBeachArcs.Add(arc); }
/* Function: handleSiteEvent * ------------------------- * Handles site event. */ private void handleSiteEvent(Site site) { // Locate arc above new site (binary search tree on x-coord) BeachArc leftArc = null; BeachArc rightArc = null; findNewArcPosition(site.x, site.y, ref leftArc, ref rightArc); // Create a new site event for the site and add it to RB-tree BeachArc newArc = createNewBeachArc(site); beachLine.Insert(leftArc, newArc); // if leftArc null, newArc set as root // First element in beach line, do nothing if (!leftArc && !rightArc) { return; } // Node falls in middle of existing beach section arc else if (leftArc == rightArc) { // Delete potential circle event of now split section deleteCircleEvent(leftArc); // Split beach section in two rightArc = createNewBeachArc(leftArc.site); beachLine.Insert(newArc, rightArc); // New edge between new site and the one that created split old arc (from new breakpoint) newArc.edge = rightArc.edge = createNewEdge(leftArc.site, newArc.site); // Check for potential circle events and add to the event queue findPotentialCircleEvent(leftArc); findPotentialCircleEvent(rightArc); } // Node falls last on beach line to the right else if (leftArc && rightArc == null) { newArc.edge = createNewEdge(leftArc.site, newArc.site); } // Only occurs if no nodes in tree yet (no left curve yet), handled above else if (leftArc == null && rightArc) { Debug.LogError("Error inserting first element into beachline tree."); } // Node falls inbetween two other beach section arcs else if (leftArc != rightArc) { deleteCircleEvent(leftArc); deleteCircleEvent(rightArc); // Find new cell vertex Vertex v = findNewVertex(site, leftArc.site, rightArc.site); // Connect vertex to arcs rightArc.edge.setStartVertex(leftArc.site, rightArc.site, v); newArc.edge = createNewEdge(leftArc.site, site, null, v); rightArc.edge = createNewEdge(site, rightArc.site, null, v); // Check for potential circle events and add to the event queue findPotentialCircleEvent(leftArc); findPotentialCircleEvent(rightArc); } }