using System.Collections.Generic; using System.Linq; using Assets.Common; using Assets.Map; 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 Point Point { get; internal set; } public int Index { get; internal set; } public List Vertices { get; internal set; } = new List(); public List<(int, int)> Edges { get; internal set; } = new List<(int, int)>(); public Site(Point point, int index) { Point = point; Index = index; } } public class VoronoiGenerator { public IList Sites { get; } public IPriorityQueue Queue { get; internal set; } public Graph Voronoi; public Graph Delaunay; public List HalfEdges; public BeachLine Line { get; private set; } public bool Done => Queue.Count == 0; public VoronoiGenerator(IList sites) { int i = 0; Sites = sites.Select(x => new Site(x, i++)).ToList(); Reset(); } private void Reset() { Queue = new SimplePriorityQueue(); Line = new BeachLine(); Voronoi = new Graph(); Delaunay = new Graph(); HalfEdges = new List(); Delaunay.Vertices.AddRange(Sites); 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 node = @event.node; 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; Voronoi.Vertices.Add(@event.vertex); var index = Voronoi.Vertices.Count - 1; node.Value.LeftEdge.End = @event.vertex; node.Value.LeftEdge.EndVertex = index; node.Value.RightEdge.End = @event.vertex; node.Value.RightEdge.EndVertex = index; HalfEdges.Add(node.Value.LeftEdge); HalfEdges.Add(node.Value.RightEdge); node.Value.Site.Vertices.Add(index); previous?.Value.Site.Vertices.Add(index); next?.Value.Site.Vertices.Insert(0, index); if (node.Value.LeftEdge.IsComplete) { var edge = node.Value.LeftEdge.Edge; node.Value.Site.Edges.Insert(0, EnsureClockwise(node.Value, edge)); previous?.Value.Site.Edges.Add(EnsureClockwise(previous.Value, edge)); Voronoi.AddEdge(edge.Item1, edge.Item2); } if (node.Value.RightEdge.IsComplete) { var edge = node.Value.RightEdge.Edge; node.Value.Site.Edges.Add(EnsureClockwise(node.Value, edge)); next?.Value.Site.Edges.Insert(0, EnsureClockwise(next.Value, edge)); Voronoi.AddEdge(edge.Item1, edge.Item2); } var newEdge = new HalfEdge() { Start = @event.vertex, StartVertex = index }; 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) Delaunay.AddEdge(next.Value.Site.Index, previous.Value.Site.Index); } private (int, int) EnsureClockwise(Parabola parabola, (int, int) edge) { var center = parabola.Site.Point; return !PointUtils.IsClockwise(center, Voronoi.Vertices[edge.Item1], Voronoi.Vertices[edge.Item2]) ? (edge.Item2, edge.Item1) : edge; } 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 HalfEdge() { Start = start }; var newRight = new HalfEdge() { Start = start, Twin = newLeft }; 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) Delaunay.AddEdge(@event.Site.Index, node.Previous.Value.Site.Index); } else { Line.AddParabola(@event.Site); } } } }