205 lines
6.0 KiB
C#
205 lines
6.0 KiB
C#
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<RedBlackNode<Point>, EdgeEvent> _events = new Dictionary<RedBlackNode<Point>, EdgeEvent>();
|
|
private RedBlackTree<Parabola> _tree = new RedBlackTree<Parabola>();
|
|
public double Directrix { get; set; } = 0.0f;
|
|
|
|
public IEnumerable<Parabola> 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<Parabola> AddParabola(Site site, RedBlackNode<Parabola> node = null)
|
|
{
|
|
if (node == null)
|
|
{
|
|
node = new RedBlackNode<Parabola>(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> parabola)
|
|
{
|
|
_tree.Remove(parabola);
|
|
}
|
|
|
|
public RedBlackNode<Parabola> 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<Parabola> node)
|
|
{
|
|
return node.Previous == null
|
|
? double.NegativeInfinity
|
|
: IntersectParabola(node.Previous.Value.Site.Point, node.Value.Site.Point);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public double Right(RedBlackNode<Parabola> 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<Parabola> 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<Parabola> 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);
|
|
}
|
|
}
|
|
} |