using System.Collections.Generic; using System.Linq; using Priority_Queue; namespace Assets.Voronoi { public interface IEvent { double Priority { get; } bool IsValid { get; } } public class SiteEvent : IEvent { public Site Site { get; internal set; } public double Priority => Site.Point.y; public bool IsValid => true; } public class EdgeEvent : IEvent { public RedBlackNode node; public Point vertex; public bool IsValid { get; set; } = true; public double Priority => vertex.y + Point.Dist(vertex, node.Value.Site.Point); } public class Site { public ISet Neighbours { get; internal set; } = new HashSet(); public Point Point { get; internal set; } public Site(Point point) { Point = point; } } public class VoronoiGenerator { public IList Sites { get; } public IPriorityQueue Queue { get; internal set; } public IList Vertices { get; internal set; } public IList Edges { get; internal set; } public BeachLine Line { get; private set; } public bool Done => Queue.Count == 0; public VoronoiGenerator(IList sites) { Sites = sites.Select(x => new Site(x)).ToList(); Reset(); } private void Reset() { Queue = new SimplePriorityQueue(); Line = new BeachLine(); Vertices = new List(); Edges = new List(); foreach (var site in Sites) { var @event = new SiteEvent { Site = site }; Queue.Enqueue(@event, @event.Priority); } } public void Step() { while (Queue.Count > 0) { var ev = Queue.Dequeue(); if (!ev.IsValid) continue; Line.Directrix = ev.Priority; switch (ev) { case SiteEvent site: HandleSiteEvent(site); return; case EdgeEvent edge: HandleEdgeEvent(edge); return; } } } public void Generate() { Reset(); while (Queue.Count > 0) Step(); } private void Enqueue(EdgeEvent @event) { if (@event.Priority > Line.Directrix) Queue.Enqueue(@event, @event.Priority); } private void HandleEdgeEvent(EdgeEvent @event) { var queue = new Queue>(); var node = @event.node; var parabola = node.Value; var previous = node.Previous; var next = node.Next; if (previous?.Value.Event != null) previous.Value.Event.IsValid = false; if (next?.Value.Event != null) next.Value.Event.IsValid = false; Vertices.Add(@event.vertex); node.Value.LeftEdge.end = @event.vertex; node.Value.RightEdge.end = @event.vertex; Edges.Add(node.Value.LeftEdge); Edges.Add(node.Value.RightEdge); var newEdge = new Edge() { start = @event.vertex }; node.Previous.Value.RightEdge = newEdge; node.Next.Value.LeftEdge = newEdge; Line.RemoveParabola(node); if (Line.CheckCircleEvent(previous) is EdgeEvent p) Enqueue(p); if (Line.CheckCircleEvent(next) is EdgeEvent n) Enqueue(n); if (previous != null && next != null) { previous.Value.Site.Neighbours.Add(next.Value.Site); next.Value.Site.Neighbours.Add(previous.Value.Site); } } private void HandleSiteEvent(SiteEvent @event) { var site = @event.Site; var start = new Point(site.Point.x, Line.Eval(site.Point.x)); var above = Line.FindParabola(site.Point.x); if (above != null) { var left = above.Value.LeftEdge; var right = above.Value.RightEdge; var node = Line.AddParabola(@event.Site, above); var newLeft = new Edge() { start = start }; var newRight = new Edge() { start = start }; node.Previous.Value.LeftEdge = left; node.Previous.Value.RightEdge = newLeft; node.Value.LeftEdge = newLeft; node.Value.RightEdge = newRight; node.Next.Value.LeftEdge = newRight; node.Next.Value.RightEdge = right; if (Line.CheckCircleEvent(node.Previous) is EdgeEvent p) Enqueue(p); if (Line.CheckCircleEvent(node.Next) is EdgeEvent n) Enqueue(n); if (node.Previous != null) { @event.Site.Neighbours.Add(node.Previous.Value.Site); node.Previous.Value.Site.Neighbours.Add(@event.Site); } } else { Line.AddParabola(@event.Site); } } } }