コード例 #1
0
ファイル: Program.cs プロジェクト: y08git/voronoi
        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;
            }
        }
コード例 #2
0
ファイル: Program.cs プロジェクト: y08git/voronoi
        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));
            }
        }
コード例 #3
0
ファイル: Program.cs プロジェクト: y08git/voronoi
        public void ExecuteToCompletion(FortunesAlgorithmState state)
        {
            while (state.EventQueue.Any())
            {
                ExecuteNextEvent(state);
            }

            ProcessCleanup(state);
        }
コード例 #4
0
ファイル: Program.cs プロジェクト: y08git/voronoi
        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
                        ));
            }
        }
コード例 #5
0
ファイル: Program.cs プロジェクト: y08git/voronoi
        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);
            }
        }
コード例 #6
0
ファイル: Program.cs プロジェクト: y08git/voronoi
        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);
            }
        }
コード例 #7
0
ファイル: Program.cs プロジェクト: y08git/voronoi
        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);
        }
コード例 #8
0
ファイル: Program.cs プロジェクト: y08git/voronoi
        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);
            }
        }
コード例 #9
0
ファイル: Program.cs プロジェクト: y08git/voronoi
        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;
//                }
//            }
        }