using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using static System.Math; namespace Assets.Voronoi { public class Edge { public Point start = null; public Point end = null; } public class Parabola { public Parabola(Site site) { Site = site; } public Site Site { get; internal set; } public EdgeEvent Event { get; internal set; } public Edge LeftEdge { get; set; } public Edge RightEdge { get; set; } public override string ToString() { return $"({Site.Point.x}, {Site.Point.y})"; } } public class BeachLine { private IDictionary, EdgeEvent> _events = new Dictionary, EdgeEvent>(); private RedBlackTree _tree = new RedBlackTree(); public double Directrix { get; set; } = 0.0f; public IEnumerable Parabolas { get { if (!_tree.IsEmpty) { var current = _tree.Root; while (current.Left != null) current = current.Left; while (current != null) { yield return current.Value; current = current.Next; } } } } public RedBlackNode AddParabola(Site site, RedBlackNode node = null) { if (node == null) { node = new RedBlackNode(new Parabola(site)); _tree.Root = node; } else { if (node.Value.Event != null) node.Value.Event.IsValid = false; _tree.InsertBefore(node, new Parabola(node.Value.Site)); _tree.InsertAfter(node, new Parabola(node.Value.Site)); node.Value = new Parabola(site); } return node; } public void RemoveParabola(RedBlackNode parabola) { _tree.Remove(parabola); } public RedBlackNode FindParabola(double x) { if (_tree.IsEmpty) return null; var current = _tree.Root; while (current != null) { if (x < Left(current)) { current = current.Left; } else if (x > Right(current)) { current = current.Right; } else { return current; } } throw new Exception("WTF?"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public double Left(RedBlackNode node) { return node.Previous == null ? double.NegativeInfinity : IntersectParabola(node.Previous.Value.Site.Point, node.Value.Site.Point); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public double Right(RedBlackNode node) { return node.Next == null ? double.PositiveInfinity : IntersectParabola(node.Value.Site.Point, node.Next.Value.Site.Point); } public double EvalParabola(Point f, double x) { var nominator = Pow(x - f.x, 2) + f.y * f.y - Directrix * Directrix; return nominator / (2 * (f.y - Directrix)); } public double Eval(double x) { var parabola = FindParabola(x); if (parabola is null) return Directrix; return EvalParabola(parabola.Value.Site.Point, x); } private Point Circle(RedBlackNode node) { if (node.Previous == null || node.Next == null) return null; var a = node.Previous.Value.Site.Point; var b = node.Value.Site.Point; var c = node.Next.Value.Site.Point; if ((a.x - b.x) * (c.y - b.y) - (c.x - b.x) * (a.y - b.y) > 0.000001) return null; var A = a.x * (b.y - c.y) - a.y * (b.x - c.x) + b.x * c.y - c.x * b.y; var B = (a.x * a.x + a.y * a.y) * (c.y - b.y) + (b.x * b.x + b.y * b.y) * (a.y - c.y) + (c.x * c.x + c.y * c.y) * (b.y - a.y); var C = (a.x * a.x + a.y * a.y) * (b.x - c.x) + (b.x * b.x + b.y * b.y) * (c.x - a.x) + (c.x * c.x + c.y * c.y) * (a.x - b.x); if (Math.Abs(A) < 0.1) return null; var x = -B / (2 * A); var y = -C / (2 * A); return new Point(x, y); } public EdgeEvent CheckCircleEvent(RedBlackNode node) { if (node == null) return null; var circle = Circle(node); if (circle == null) return null; return node.Value.Event = new EdgeEvent() { vertex = circle, node = node }; } private double IntersectParabola(Point q, Point p) { if (Abs(p.y - Directrix) < 0.0001) return p.x; if (Abs(q.y - Directrix) < 0.0001) return q.x; if (Abs(p.y - q.y) < 0.0001) return (q.x + p.x) / 2; var alpha = 1 / (2 * (q.y - Directrix)); var beta = 1 / (2 * (p.y - Directrix)); double a = alpha - beta; double b = -2 * alpha * q.x + 2 * beta * p.x; double c = alpha * (Pow(q.x, 2) + Pow(q.y, 2) - Pow(Directrix, 2)) - beta * (Pow(p.x, 2) + Pow(p.y, 2) - Pow(Directrix, 2)); double d = Sqrt(b * b - 4.0 * a * c); return (-b - d) / (2 * a); } } }