before candybox editor update
This commit is contained in:
79
Plugins/CandyboxPlugin/Voronoi/Delaunay.cs
Normal file
79
Plugins/CandyboxPlugin/Voronoi/Delaunay.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
namespace CandyboxPlugin.Voronoi
|
||||
{
|
||||
public class DelaunayTriangulator
|
||||
{
|
||||
private double MaxX { get; set; }
|
||||
private double MaxY { get; set; }
|
||||
private IEnumerable<Triangle> border;
|
||||
public void GenerateBorder(double maxX, double maxY)
|
||||
{
|
||||
MaxX = maxX;
|
||||
MaxY = maxY;
|
||||
|
||||
// TODO make more beautiful
|
||||
var point0 = new VoronoiPoint(0, 0);
|
||||
var point1 = new VoronoiPoint(0, MaxY);
|
||||
var point2 = new VoronoiPoint(MaxX, MaxY);
|
||||
var point3 = new VoronoiPoint(MaxX, 0);
|
||||
var points = new List<VoronoiPoint>() { point0, point1, point2, point3 };
|
||||
var tri1 = new Triangle(point0, point1, point2);
|
||||
var tri2 = new Triangle(point0, point2, point3);
|
||||
border = new List<Triangle>() { tri1, tri2 };
|
||||
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<Triangle> BowyerWatson(IEnumerable<VoronoiPoint> points)
|
||||
{
|
||||
//var supraTriangle = GenerateSupraTriangle();
|
||||
var triangulation = new HashSet<Triangle>(border);
|
||||
|
||||
foreach (var point in points)
|
||||
{
|
||||
var badTriangles = FindBadTriangles(point, triangulation);
|
||||
var polygon = FindHoleBoundaries(badTriangles);
|
||||
|
||||
foreach (var triangle in badTriangles)
|
||||
{
|
||||
foreach (var vertex in triangle.Vertices)
|
||||
{
|
||||
vertex.AdjacentTriangles.Remove(triangle);
|
||||
}
|
||||
}
|
||||
triangulation.RemoveWhere(o => badTriangles.Contains(o));
|
||||
|
||||
foreach (var edge in polygon.Where(possibleEdge => possibleEdge.Point1 != point && possibleEdge.Point2 != point))
|
||||
{
|
||||
|
||||
var triangle = new Triangle(point, edge.Point1, edge.Point2);
|
||||
triangulation.Add(triangle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return triangulation;
|
||||
}
|
||||
|
||||
private List<Edge> FindHoleBoundaries(ISet<Triangle> badTriangles)
|
||||
{
|
||||
var edges = new List<Edge>();
|
||||
foreach (var triangle in badTriangles)
|
||||
{
|
||||
edges.Add(new Edge(triangle.Vertices[0], triangle.Vertices[1]));
|
||||
edges.Add(new Edge(triangle.Vertices[1], triangle.Vertices[2]));
|
||||
edges.Add(new Edge(triangle.Vertices[2], triangle.Vertices[0]));
|
||||
}
|
||||
var grouped = edges.GroupBy(o => o);
|
||||
var boundaryEdges = edges.GroupBy(o => o).Where(o => o.Count() == 1).Select(o => o.First());
|
||||
return boundaryEdges.ToList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ISet<Triangle> FindBadTriangles(VoronoiPoint point, HashSet<Triangle> triangles)
|
||||
{
|
||||
var badTriangles = triangles.Where(o => o.IsPointInsideCircumcircle(point));
|
||||
return new HashSet<Triangle>(badTriangles);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Plugins/CandyboxPlugin/Voronoi/Edge.cs
Normal file
31
Plugins/CandyboxPlugin/Voronoi/Edge.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace CandyboxPlugin.Voronoi
|
||||
{
|
||||
public class Edge
|
||||
{
|
||||
public VoronoiPoint Point1 { get; }
|
||||
public VoronoiPoint Point2 { get; }
|
||||
|
||||
public Edge(VoronoiPoint point1, VoronoiPoint point2)
|
||||
{
|
||||
Point1 = point1;
|
||||
Point2 = point2;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null) return false;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
var edge = obj as Edge;
|
||||
|
||||
var samePoints = Point1 == edge.Point1 && Point2 == edge.Point2;
|
||||
var samePointsReversed = Point1 == edge.Point2 && Point2 == edge.Point1;
|
||||
return samePoints || samePointsReversed;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hCode = (int)Point1.X ^ (int)Point1.Y ^ (int)Point2.X ^ (int)Point2.Y;
|
||||
return hCode.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
113
Plugins/CandyboxPlugin/Voronoi/Triangle.cs
Normal file
113
Plugins/CandyboxPlugin/Voronoi/Triangle.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
namespace CandyboxPlugin.Voronoi
|
||||
{
|
||||
public class Triangle
|
||||
{
|
||||
|
||||
public VoronoiPoint[] Vertices { get; } = new VoronoiPoint[3];
|
||||
public VoronoiPoint Circumcenter { get; private set; }
|
||||
public double RadiusSquared;
|
||||
|
||||
public IEnumerable<Triangle> TrianglesWithSharedEdge {
|
||||
get {
|
||||
var neighbors = new HashSet<Triangle>();
|
||||
foreach (var vertex in Vertices)
|
||||
{
|
||||
var trianglesWithSharedEdge = vertex.AdjacentTriangles.Where(o =>
|
||||
{
|
||||
return o != this && SharesEdgeWith(o);
|
||||
});
|
||||
neighbors.UnionWith(trianglesWithSharedEdge);
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Triangle(VoronoiPoint point1, VoronoiPoint point2, VoronoiPoint point3)
|
||||
{
|
||||
// In theory this shouldn't happen, but it was at one point so this at least makes sure we're getting a
|
||||
// relatively easily-recognised error message, and provides a handy breakpoint for debugging.
|
||||
if (point1 == point2 || point1 == point3 || point2 == point3)
|
||||
{
|
||||
throw new ArgumentException("Must be 3 distinct points");
|
||||
}
|
||||
|
||||
if (!IsCounterClockwise(point1, point2, point3))
|
||||
{
|
||||
Vertices[0] = point1;
|
||||
Vertices[1] = point3;
|
||||
Vertices[2] = point2;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vertices[0] = point1;
|
||||
Vertices[1] = point2;
|
||||
Vertices[2] = point3;
|
||||
}
|
||||
|
||||
Vertices[0].AdjacentTriangles.Add(this);
|
||||
Vertices[1].AdjacentTriangles.Add(this);
|
||||
Vertices[2].AdjacentTriangles.Add(this);
|
||||
UpdateCircumcircle();
|
||||
}
|
||||
|
||||
private void UpdateCircumcircle()
|
||||
{
|
||||
// https://codefound.wordpress.com/2013/02/21/how-to-compute-a-circumcircle/#more-58
|
||||
// https://en.wikipedia.org/wiki/Circumscribed_circle
|
||||
var p0 = Vertices[0];
|
||||
var p1 = Vertices[1];
|
||||
var p2 = Vertices[2];
|
||||
var dA = p0.X * p0.X + p0.Y * p0.Y;
|
||||
var dB = p1.X * p1.X + p1.Y * p1.Y;
|
||||
var dC = p2.X * p2.X + p2.Y * p2.Y;
|
||||
|
||||
var aux1 = (dA * (p2.Y - p1.Y) + dB * (p0.Y - p2.Y) + dC * (p1.Y - p0.Y));
|
||||
var aux2 = -(dA * (p2.X - p1.X) + dB * (p0.X - p2.X) + dC * (p1.X - p0.X));
|
||||
var div = (2 * (p0.X * (p2.Y - p1.Y) + p1.X * (p0.Y - p2.Y) + p2.X * (p1.Y - p0.Y)));
|
||||
|
||||
if (div == 0)
|
||||
{
|
||||
throw new DivideByZeroException();
|
||||
}
|
||||
|
||||
var center = new VoronoiPoint(aux1 / div, aux2 / div);
|
||||
Circumcenter = center;
|
||||
RadiusSquared = (center.X - p0.X) * (center.X - p0.X) + (center.Y - p0.Y) * (center.Y - p0.Y);
|
||||
}
|
||||
|
||||
private bool IsCounterClockwise(VoronoiPoint point1, VoronoiPoint point2, VoronoiPoint point3)
|
||||
{
|
||||
var result = (point2.X - point1.X) * (point3.Y - point1.Y) -
|
||||
(point3.X - point1.X) * (point2.Y - point1.Y);
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
public bool SharesEdgeWith(Triangle triangle)
|
||||
{
|
||||
var sharedVertices = Vertices.Where(o => triangle.Vertices.Contains(o)).Count();
|
||||
return sharedVertices == 2;
|
||||
}
|
||||
|
||||
public bool IsPointInsideCircumcircle(VoronoiPoint point)
|
||||
{
|
||||
var d_squared = (point.X - Circumcenter.X) * (point.X - Circumcenter.X) +
|
||||
(point.Y - Circumcenter.Y) * (point.Y - Circumcenter.Y);
|
||||
return d_squared < RadiusSquared;
|
||||
}
|
||||
|
||||
public bool IsInside(VoronoiPoint point)
|
||||
{
|
||||
var b1 = Sign(point, Vertices[0], Vertices[1]) < 0.0;
|
||||
var b2 = Sign(point, Vertices[1], Vertices[2]) < 0.0;
|
||||
var b3 = Sign(point, Vertices[2], Vertices[0]) < 0.0;
|
||||
return ((b1 == b2) && (b2 == b3));
|
||||
}
|
||||
|
||||
private double Sign(VoronoiPoint p1, VoronoiPoint p2, VoronoiPoint p3)
|
||||
{
|
||||
return (p1.X - p3.X) * (p2.Y - p3.Y) - (p2.X - p3.X) * (p1.Y - p3.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Plugins/CandyboxPlugin/Voronoi/Voronoi.cs
Normal file
23
Plugins/CandyboxPlugin/Voronoi/Voronoi.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace CandyboxPlugin.Voronoi
|
||||
{
|
||||
public class Voronoi
|
||||
{
|
||||
public static List<Edge> GenerateEdgesFromDelaunay(IEnumerable<Triangle> triangulation)
|
||||
{
|
||||
var voronoiEdges = new HashSet<Edge>();
|
||||
foreach (var triangle in triangulation)
|
||||
{
|
||||
foreach (var neighbor in triangle.TrianglesWithSharedEdge)
|
||||
{
|
||||
|
||||
var edge = new Edge(triangle.Circumcenter, neighbor.Circumcenter);
|
||||
voronoiEdges.Add(edge);
|
||||
}
|
||||
}
|
||||
|
||||
return voronoiEdges.ToList();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
32
Plugins/CandyboxPlugin/Voronoi/VoronoiPoint.cs
Normal file
32
Plugins/CandyboxPlugin/Voronoi/VoronoiPoint.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace CandyboxPlugin.Voronoi
|
||||
{
|
||||
public class VoronoiPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// Used only for generating a unique ID for each instance of this class that gets generated
|
||||
/// </summary>
|
||||
private static int _counter;
|
||||
|
||||
/// <summary>
|
||||
/// Used for identifying an instance of a class; can be useful in troubleshooting when geometry goes weird
|
||||
/// (e.g. when trying to identify when Triangle objects are being created with the same Point object twice)
|
||||
/// </summary>
|
||||
private readonly int _instanceId = _counter++;
|
||||
|
||||
public double X { get; }
|
||||
public double Y { get; }
|
||||
public HashSet<Triangle> AdjacentTriangles { get; } = new HashSet<Triangle>();
|
||||
|
||||
public VoronoiPoint(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// Simple way of seeing what's going on in the debugger when investigating weirdness
|
||||
return $"{nameof(VoronoiPoint)} {_instanceId} {X:0.##}@{Y:0.##}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user