113 lines
4.1 KiB
C#
113 lines
4.1 KiB
C#
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);
|
|
}
|
|
}
|
|
} |