public void ProcessCleanup(FortunesAlgorithmState state) { var s = new Stack <Node>(); s.Push(state.Root); while (s.Any()) { var current = s.Pop(); if (current == null) { continue; } var edgeRayData = current.Data as EdgeRayData; if (edgeRayData == null) { continue; } s.Push(current.Left); s.Push(current.Right); state.VoronoiEdges.Add( new VoronoiEdge( edgeRayData.Origin, edgeRayData.IsOriginBounded, edgeRayData.Origin + edgeRayData.Direction, false)); state.Root = null; } }
private void ProcessFirstSiteEvents(FortunesAlgorithmState state) { var initialSiteEvent = (SiteEvent)state.EventQueue.Dequeue(); // Pull other site events with the same y-value var siteEvents = new List <SiteEvent> { initialSiteEvent }; ISweepEvent sweepEvent; while (state.EventQueue.TryPeek(out sweepEvent) && MathUtil.ApproximatelyEqual(sweepEvent.Y, initialSiteEvent.Y)) { state.EventQueue.Dequeue(); siteEvents.Add((SiteEvent)sweepEvent); } // Order by X and divide and conquer to build beachfront tree. siteEvents.Sort((a, b) => a.Point.X.CompareTo(b.Point.X)); state.Root = BuildInitialBeachFront(siteEvents, 0, siteEvents.Count); foreach (var pair in siteEvents.Zip(siteEvents.Skip(1), Tuple.Create)) { state.DelanayEdges.Add(new VoronoiEdge(pair.Item1.Point, true, pair.Item2.Point, true)); } }
public void ExecuteToCompletion(FortunesAlgorithmState state) { while (state.EventQueue.Any()) { ExecuteNextEvent(state); } ProcessCleanup(state); }
void HandleAddCircleEvent( FortunesAlgorithmState state, Node leftParabola, Node centerParabola, Node rightParabola, TNumber sweepY) { var leftParabolaFocus = ((ParabolaData)leftParabola.Data).Focus; var centerParabolaFocus = ((ParabolaData)centerParabola.Data).Focus; var rightParabolaFocus = ((ParabolaData)rightParabola.Data).Focus; // center gets swallowed by left/right when sweep line hits bottom of foci circumcircle TVector2 circumcenter; TNumber radius; MathUtil.FindCircumcircle( leftParabolaFocus, centerParabolaFocus, rightParabolaFocus, out circumcenter, out radius); // All three points have a circumcenter - the parabola-swallowing // circle event will only happen if edge rays point towards the circumcenter. Node leftAncestor, rightAncestor; centerParabola.FindDirectionalAncestors(out leftAncestor, out rightAncestor); var leftEdgeRayData = (EdgeRayData)leftAncestor.Data; var rightEdgeRayData = (EdgeRayData)rightAncestor.Data; var leftEdgeOriginToCircumcenter = circumcenter - leftEdgeRayData.Origin; var rightEdgeOriginToCircumcenter = circumcenter - rightEdgeRayData.Origin; if (TVector2.Dot(leftEdgeOriginToCircumcenter, leftEdgeRayData.Direction) <= 0 || TVector2.Dot(rightEdgeOriginToCircumcenter, rightEdgeRayData.Direction) <= 0) { return; } if (circumcenter.Y + radius > sweepY) { state.EventQueue.Enqueue( new CircleEvent( circumcenter, radius, centerParabola )); } }
private void ProcessSiteEvent(FortunesAlgorithmState state, SiteEvent e) { var sweepY = e.Y; var newParabolaFocus = e.Point; if (state.Root == null) { state.Root = new Node(new ParabolaData(newParabolaFocus)); return; } var cutNode = state.Root.FindDeepestNodeAtX(newParabolaFocus.X, newParabolaFocus.Y); if (!cutNode.IsLeaf()) { throw new NotSupportedException("Cutting edge node not supported."); } var cutParabolaFocus = ((ParabolaData)cutNode.Data).Focus; Node leftParabola, centerParabola, rightParabola; Node newNode = BeachlineNodeOperations.ComputeThreeParabolasFromDifferentYParabolaNodeCut( cutParabolaFocus, newParabolaFocus, out leftParabola, out centerParabola, out rightParabola); NodeOperations.ReplaceNode(cutNode, newNode, ref state.Root); state.DelanayEdges.Add(new VoronoiEdge(newParabolaFocus, true, cutParabolaFocus, true)); Node leftLeftParabola; if (leftParabola.TryGetLeftLeaf(out leftLeftParabola)) { HandleAddCircleEvent(state, leftLeftParabola, leftParabola, centerParabola, sweepY); } Node rightRightParabola; if (rightParabola.TryGetRightLeaf(out rightRightParabola)) { HandleAddCircleEvent(state, centerParabola, rightParabola, rightRightParabola, sweepY); } }
public void ExecuteNextEvent(FortunesAlgorithmState state) { if (state.Root == null) { ProcessFirstSiteEvents(state); return; } ISweepEvent sweepEvent = state.EventQueue.Dequeue(); if (sweepEvent.Type == SweepEventType.Site) { ProcessSiteEvent(state, (SiteEvent)sweepEvent); } else { ProcessCircleEvent(state, (CircleEvent)sweepEvent); } }
public Bitmap Render(FortunesAlgorithmState state, TNumber?sweepY = null) { state.Root.ValidateTree(); var bitmap = new Bitmap(ImageSize.Width, ImageSize.Height, PixelFormat.Format24bppRgb); using (var g = Graphics.FromImage(bitmap)) { g.CompositingQuality = CompositingQuality.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.SmoothingMode = SmoothingMode.AntiAlias; g.Clear(Color.FromArgb(236, 240, 241)); g.TranslateTransform(Padding.Left, Padding.Top); foreach (var initialPoint in state.InitialPoints) { DrawPointSquare(g, initialPoint, 3); } foreach (var edge in state.VoronoiEdges) { var start = edge.Start; var end = edge.End; var direction = end - start; if (!edge.IsStartBounded) { start = end - direction * (end.Y + Padding.Top); } if (!edge.IsEndBounded) { end += direction * 100; } g.DrawLine(_settledVoronoiEdgePen, start.X, start.Y, end.X, end.Y); } foreach (var edge in state.DelanayEdges) { var start = edge.Start; var end = edge.End; g.DrawLine(_delaunayTriangulationEdgePen, start.X, start.Y, end.X, end.Y); } Node highlightedNode = null; var eventQueueCopy = state.EventQueue.Copy(); ISweepEvent sweepEvent; var eventCounter = 0; while (eventQueueCopy.TryDequeue(out sweepEvent)) { var eventPoint = sweepEvent.Point; // g.DrawString(eventCounter.ToString(), _font, Brushes.Black, eventPoint.X, eventPoint.Y); if (sweepEvent is CircleEvent) { var circleEvent = (CircleEvent)sweepEvent; if (eventCounter == 0) { highlightedNode = circleEvent.SwallowedParabolaNode; } var pen = eventCounter == 0 ? _nextCircleEventPen : _futureCircleEventPen; DrawPointX(g, eventPoint, 3, pen); g.DrawEllipse( pen, circleEvent.Circumcenter.X - circleEvent.Radius, circleEvent.Circumcenter.Y - circleEvent.Radius, circleEvent.Radius * 2, circleEvent.Radius * 2 ); } else { var siteEvent = (SiteEvent)sweepEvent; if (state.Root != null && sweepY.HasValue && eventCounter == 0) { var parabolaNode = state.Root.FindDeepestNodeAtX(siteEvent.Point.X, siteEvent.Y); var parabolaData = (ParabolaData)parabolaNode.Data; var parabolaPoint = MathUtil.ComputeParabolaPointGivenX( siteEvent.Point.X, sweepY.Value, parabolaData.Focus); if (!float.IsInfinity(parabolaPoint.Y)) { g.DrawLine(Pens.Black, parabolaPoint.X, parabolaPoint.Y, siteEvent.Point.X, siteEvent.Point.Y); } } } eventCounter++; } if (sweepY.HasValue) { DrawBeachlineNode(g, state.Root, sweepY.Value, highlightedNode); g.DrawLine(Pens.Black, -Padding.Left, (int)sweepY, -Padding.Left + ImageSize.Width, (int)sweepY); } } return(bitmap); }
void ProcessCircleEvent(FortunesAlgorithmState state, CircleEvent e) { var sweepY = e.Y; var swallowedParabolaNode = e.SwallowedParabolaNode; if (swallowedParabolaNode.Parent == null) { return; } Node leftAncestor, rightAncestor; swallowedParabolaNode.FindDirectionalAncestors(out leftAncestor, out rightAncestor); var leftAncestorEdgeRayData = (EdgeRayData)leftAncestor.Data; var rightAncestorEdgeRayData = (EdgeRayData)rightAncestor.Data; state.VoronoiEdges.Add(new VoronoiEdge( leftAncestorEdgeRayData.Origin, leftAncestorEdgeRayData.IsOriginBounded, e.Circumcenter, true)); state.VoronoiEdges.Add(new VoronoiEdge( rightAncestorEdgeRayData.Origin, leftAncestorEdgeRayData.IsOriginBounded, e.Circumcenter, true)); var leftParabolaNode = leftAncestor.Left.GetRightmost(); var leftParabolaFocus = ((ParabolaData)leftParabolaNode.Data).Focus; var rightParabolaNode = rightAncestor.Right.GetLeftmost(); var rightParabolaFocus = ((ParabolaData)rightParabolaNode.Data).Focus; state.DelanayEdges.Add(new VoronoiEdge(leftParabolaFocus, true, rightParabolaFocus, true)); // One ancestor will become the edge shared by the left/right parabolas, // the other will be deleted. It's guranteed our parent is one of these // ancestors and that the other ancestor is above it (that is, it has a // parent) so opt to delete our parent. var nodeParent = swallowedParabolaNode.Parent; var nodeSibling = nodeParent.Left == swallowedParabolaNode ? nodeParent.Right : nodeParent.Left; NodeOperations.ReplaceNode(nodeParent, nodeSibling, ref state.Root); nodeParent.Left = nodeParent.Right = null; var olderAncestor = nodeParent == leftAncestor ? rightAncestor : leftAncestor; var leftFocusRightFocus = rightParabolaFocus - leftParabolaFocus; // x is positive var edgeDirection = new TVector2(-leftFocusRightFocus.Y, leftFocusRightFocus.X); var leftRightFocusCenter = new TVector2( MathUtil.Average(leftParabolaFocus.X, rightParabolaFocus.X), MathUtil.Average(leftParabolaFocus.Y, rightParabolaFocus.Y)); var dy = sweepY - leftRightFocusCenter.Y; var edgeStart = e.Circumcenter; olderAncestor.Data = new EdgeRayData(true, edgeStart, edgeDirection); // add new potential circle events Node leftLeftLeaf; if (leftParabolaNode.TryGetLeftLeaf(out leftLeftLeaf)) { HandleAddCircleEvent(state, leftLeftLeaf, leftParabolaNode, rightParabolaNode, sweepY); } Node rightRightLeaf; if (rightParabolaNode.TryGetRightLeaf(out rightRightLeaf)) { HandleAddCircleEvent(state, leftParabolaNode, rightParabolaNode, rightRightLeaf, sweepY); } }
public static void Main(string[] args) { var points = new HashSet <Vector2> { new Vector2(100, 100), new Vector2(200, 100), new Vector2(120, 150), new Vector2(180, 150) }; // var scale = 1f; // var points = new HashSet<Vector2> // { // new Vector2(100 * scale, 100 * scale), // new Vector2(250 * scale, 100 * scale), // new Vector2(160 * scale, 150 * scale), // new Vector2(180 * scale, 160 * scale) // }; var random = new Random(1); points = new HashSet <Vector2>( Enumerable.Range(0, 300) .Select(i => { var y = (TNumber)random.NextDouble() * 1200; var x = (TNumber)random.NextDouble() * 500 + y / 2; return(new TVector2(x, y)); }) ); // points = new HashSet<TVector2>( // from i in Enumerable.Range(0, 4) // from j in Enumerable.Range(0, 4) // let shift = i % 2 == 0 ? 25 : 0 // let spacing = 50 // select new TVector2(j * spacing + shift, i * spacing) // ); var renderer = FortunesAlgorithmRenderer.Create(points); var display = ImageDisplay.CreateAndRunInBackground(renderer.ImageSize); var state = FortunesAlgorithmState.CreateAndInitialize(points); var fortunesAlgorithmExecutor = new FortunesAlgorithmExecutor(); // // show end result // fortunesAlgorithmExecutor.ExecuteToCompletion(state); // display.AddFrame(renderer.Render(state)); // animate var frames = new ObservableCollection <Bitmap>(); frames.CollectionChanged += (s, e) => display.AddFrame((Bitmap)e.NewItems[0]); TNumber sweepY = -renderer.Padding.Top; ISweepEvent sweepEvent; var frame = 0; while (state.EventQueue.TryPeek(out sweepEvent)) { // while (sweepY < sweepEvent.Y) // { // TNumber stepSize = 1; // var bottomY = renderer.BoardSize.Height + renderer.Padding.Bottom; // if (sweepY > bottomY) // { // var speed = (TNumber)Math.Pow(sweepY - bottomY, 1.01); // stepSize = Math.Max(speed, stepSize); // } // frames.Add(renderer.Render(state, sweepY)); // sweepY += stepSize; // } fortunesAlgorithmExecutor.ExecuteNextEvent(state); frame++; if (frame % 10 == 0) { frames.Add(renderer.Render(state, sweepEvent.Y + 0.1f)); } if (frames.Count == 115) { var old = renderer.ImageSize; var padding = renderer.Padding = new Padding(3200, 3200, 3200, 3200); renderer.ImageSize = new Size(renderer.BoardSize.Width + padding.Horizontal, renderer.BoardSize.Height + padding.Vertical); renderer.Render(state, sweepEvent.Y + 0.1f).Save(@"V:\my-repositories\miyu\voronoi\image.png", ImageFormat.Png); Environment.Exit(0); } if (state.EventQueue.Count <= 5) { fortunesAlgorithmExecutor.ExecuteToCompletion(state); } } // while (sweepY < renderer.BoardSize.Height + renderer.Padding.Bottom) // frames.Add(renderer.Render(state, sweepY++)); fortunesAlgorithmExecutor.ProcessCleanup(state); frames.Add(renderer.Render(state)); if (!Directory.Exists("output")) { Directory.CreateDirectory("output"); } // int frame = 0; // for (int i = 0; i < 10; i++) // frames.Last().Save($"output/{frame++}.gif", ImageFormat.Gif); // for (int i = frames.Count - 1; i >= 0; i -= 14) // frames[i].Save($"output/{frame++}.gif", ImageFormat.Gif); // for (int i = 0; i < frames.Count; i += 3) // frames[i].Save($"output/{frame++}.gif", ImageFormat.Gif); // using (var outputGifStream = File.OpenWrite("output.gif")) // using (var gifEncoder = new GifEncoder( // outputGifStream, renderer.ImageSize.Width, renderer.ImageSize.Height)) // { // gifEncoder.AddFrame(frames.Last(), TimeSpan.FromMilliseconds(1000)); // for (int i = frames.Count - 1; i >= 0; i -= 14) // gifEncoder.AddFrame(frames[i], TimeSpan.FromMilliseconds(10)); // for (int i = 0; i < frames.Count; i += 3) // gifEncoder.AddFrame(frames[i], TimeSpan.FromMilliseconds(10)); // } // using (MagickImageCollection collection = new MagickImageCollection()) // { // collection.Add(new MagickImage(frames.Last())); // collection[collection.Count - 1].AnimationDelay = 1000; // // for (int i = frames.Count - 1; i >= 0; i -= 10) // { // collection.Add(new MagickImage(frames[i])); // collection[collection.Count - 1].AnimationDelay = 10; // } // for (int i = 0; i < frames.Count; i++) // { // // collection.Add(new MagickImage(frames[i])); // collection[collection.Count - 1].AnimationDelay = 10; // } // } }