Skip to content

Commit

Permalink
Added 2D delaunay and voronoi in Delaunay class.
Browse files Browse the repository at this point in the history
Added specific basic tests.
Ignored coverage folder.
  • Loading branch information
christiandimitri committed Jun 13, 2020
1 parent 926aa94 commit 40478bf
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 1 deletion.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -353,4 +353,6 @@ MigrationBackup/
.vscode/settings.json

coverage/reports/
docs/*
docs/*

coverage/
94 changes: 94 additions & 0 deletions src/Geometry/2D/Delaunay.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Collections.Generic;

namespace Paramdigma.Core.Geometry
{
/// <summary>
/// Class holding all the delaunay and vornoi classes in 2 dimensions.
/// </summary>
public static class Delaunay
{

public static List<DelaunayTriangle> Compute(List<DelaunayPoint> points, List<DelaunayTriangle> border)
{
List<DelaunayTriangle> triangulation = new List<DelaunayTriangle>(border);
points.Reverse();
foreach (DelaunayPoint point in points)
{
List<DelaunayTriangle> badTriangles = FindBadTriangles(point, triangulation);
List<DelaunayEdge> polygon = FindHoleBoundaries(badTriangles);
foreach (DelaunayTriangle triangle in badTriangles)
{
foreach (DelaunayPoint vertex in triangle.Vertices)
{
vertex.AdjacentTriangles.Remove(triangle);
}
if (triangulation.Contains(triangle)) triangulation.Remove(triangle);
}
foreach (DelaunayEdge edge in polygon)
{
DelaunayTriangle triangle = new DelaunayTriangle(point, edge.StartPoint, edge.EndPoint);
triangulation.Add(triangle);
}
}
return triangulation;
}

public static List<DelaunayEdge> Voronoi(List<DelaunayTriangle> triangulation)
{
List<DelaunayEdge> voronoiEdges = new List<DelaunayEdge>();
foreach (DelaunayTriangle triangle in triangulation)
{
foreach (DelaunayTriangle neigbour in triangle.TrianglesWithSharedEdges())
{
DelaunayEdge edge = new DelaunayEdge(triangle.Circumcenter, neigbour.Circumcenter);
voronoiEdges.Add(edge);

}
}
return voronoiEdges;
}

private static List<DelaunayEdge> FindHoleBoundaries(List<DelaunayTriangle> badTriangles)
{
List<DelaunayEdge> boundaryEdges = new List<DelaunayEdge>();
List<DelaunayEdge> duplicateEdges = new List<DelaunayEdge>();
foreach (DelaunayTriangle triangle in badTriangles)
{
DelaunayEdge e = new DelaunayEdge(triangle.Vertices[0], triangle.Vertices[1]);
if (!boundaryEdges.Contains(e))
boundaryEdges.Add(e);
else
duplicateEdges.Add(e);
DelaunayEdge f = new DelaunayEdge(triangle.Vertices[1], triangle.Vertices[2]);
if (!boundaryEdges.Contains(f))
boundaryEdges.Add(f);
else
duplicateEdges.Add(f);
DelaunayEdge j = new DelaunayEdge(triangle.Vertices[2], triangle.Vertices[0]);
if (!boundaryEdges.Contains(j))
boundaryEdges.Add(j);
else
duplicateEdges.Add(j);
}

for (int i = boundaryEdges.Count - 1; i >= 0; i--)
{
DelaunayEdge e = boundaryEdges[i];
if (duplicateEdges.Contains(e))
boundaryEdges.Remove(e);
}
return boundaryEdges;
}

private static List<DelaunayTriangle> FindBadTriangles(DelaunayPoint point, List<DelaunayTriangle> triangles)
{
List<DelaunayTriangle> badTriangles = new List<DelaunayTriangle>();
foreach (DelaunayTriangle triangle in triangles)
{
if (triangle.IsPointInsideCircumcircle(point)) badTriangles.Add(triangle);
}
return badTriangles;
}

}
}
33 changes: 33 additions & 0 deletions src/Geometry/2D/DelaunayEdge.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Paramdigma.Core.Geometry
{
public class DelaunayEdge
{
public DelaunayPoint StartPoint;
public DelaunayPoint EndPoint;

public DelaunayEdge(DelaunayPoint startPoint, DelaunayPoint endPoint)
{
StartPoint = startPoint;
EndPoint = endPoint;
}

public override bool Equals(object obj)
{
if (obj == null) return false;
if (obj.GetType() != GetType()) return false;
var edge = obj as DelaunayEdge;
var samePoints = StartPoint == edge.StartPoint && EndPoint == edge.EndPoint;
var samePointsReversed = StartPoint == edge.EndPoint && EndPoint == edge.StartPoint;
return samePoints || samePointsReversed;
}

public override int GetHashCode()
{
int hCode = (int)StartPoint.X ^ (int)StartPoint.Y ^ (int)EndPoint.X ^ (int)EndPoint.Y;
return hCode.GetHashCode();
}



}
}
14 changes: 14 additions & 0 deletions src/Geometry/2D/DelaunayPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;

namespace Paramdigma.Core.Geometry
{
public class DelaunayPoint : Point2d
{
public List<DelaunayTriangle> AdjacentTriangles;

public DelaunayPoint(double x, double y) : base(x, y)
{
AdjacentTriangles = new List<DelaunayTriangle>();
}
}
}
100 changes: 100 additions & 0 deletions src/Geometry/2D/DelaunayTriangle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;

namespace Paramdigma.Core.Geometry
{
public class DelaunayTriangle
{

public List<DelaunayPoint> Vertices = new List<DelaunayPoint>();

public DelaunayPoint Circumcenter;
public double RadiusSquared;

public List<DelaunayTriangle> TrianglesWithSharedEdges()
{
List<DelaunayTriangle> neighbours = new List<DelaunayTriangle>();
foreach (DelaunayPoint vertex in Vertices)
{
foreach (DelaunayTriangle sharedTriangle in vertex.AdjacentTriangles)
{
if (this.SharesEdgeWidth(sharedTriangle) && neighbours.Contains(sharedTriangle) == false && sharedTriangle != this) neighbours.Add(sharedTriangle);

}
}
return neighbours;
}
public DelaunayTriangle(DelaunayPoint point1, DelaunayPoint point2, DelaunayPoint point3)
{

Vertices = new List<DelaunayPoint>();
if (!IsCounterClockwise(point1, point2, point3))
{
Vertices.Add(point1);
Vertices.Add(point3);
Vertices.Add(point2);
}
else
{
Vertices.Add(point1);
Vertices.Add(point2);
Vertices.Add(point3);
}
Vertices[0].AdjacentTriangles.Add(this);
Vertices[1].AdjacentTriangles.Add(this);
Vertices[2].AdjacentTriangles.Add(this);
UpdateCircumcircle();

}
public void UpdateCircumcircle()
{
DelaunayPoint p0 = Vertices[0];
DelaunayPoint p1 = Vertices[1];
DelaunayPoint p2 = Vertices[2];
double dA = p0.X * p0.X + p0.Y * p0.Y;
double dB = p1.X * p1.X + p1.Y * p1.Y;
double dC = p2.X * p2.X + p2.Y * p2.Y;

double aux1 = (dA * (p2.Y - p1.Y) + dB * (p0.Y - p2.Y) + dC * (p1.Y - p0.Y));
double aux2 = -(dA * (p2.X - p1.X) + dB * (p0.X - p2.X) + dC * (p1.X - p0.X));
double 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 System.Exception();
}

DelaunayPoint center = new DelaunayPoint(aux1 / div, aux2 / div);
Circumcenter = center;
RadiusSquared = (center.X - p0.X) * (center.X - p0.X) + (center.Y - p0.Y) * (center.Y - p0.Y);
}

public bool IsCounterClockwise(DelaunayPoint point1, DelaunayPoint point2, DelaunayPoint point3)
{
double result = (point2.X - point1.X) * (point3.Y - point1.Y) -
(point3.X - point1.X) * (point2.Y - point1.Y);
return result > 0;
}

public bool SharesEdgeWidth(DelaunayTriangle triangle)
{
int sharedCount = 0;
foreach (DelaunayPoint vertex in this.Vertices)
{
foreach (DelaunayPoint vertex2 in triangle.Vertices)
{
if (vertex == vertex2) sharedCount++;
}
}
return sharedCount == 2;

throw new NotImplementedException();
}
public bool IsPointInsideCircumcircle(DelaunayPoint point)
{
double d_squared = (point.X - Circumcenter.X) * (point.X - Circumcenter.X) +
(point.Y - Circumcenter.Y) * (point.Y - Circumcenter.Y);
return d_squared < RadiusSquared;
}
}
}
64 changes: 64 additions & 0 deletions tests/Geometry/2D/DelaunayTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using Paramdigma.Core.Geometry;
using Xunit;

namespace Paramdigma.Core.Tests.Geometry
{
public class DelaunayTests
{
[Fact]
public void CanComputeDelaunay()
{
var maxX = 100;
var maxY = 100;
var point0 = new DelaunayPoint(0, 0);
var point1 = new DelaunayPoint(0, maxY);
var point2 = new DelaunayPoint(maxX, maxY);
var point3 = new DelaunayPoint(maxX, 0);
var point4 = new DelaunayPoint(maxX/2, maxY/2);
var points = new List<DelaunayPoint>() { point0, point1, point2, point3 };
var triangle1 = new DelaunayTriangle(point0, point1, point2);
var triangle2 = new DelaunayTriangle(point0, point2, point3);
var border = new List<DelaunayTriangle> { triangle1, triangle2 };

var delaunay = Delaunay.Compute(
new List<DelaunayPoint>{point0,point1,point2,point3,point4},
border
);
Assert.True(delaunay.Count == 4);

var voronoi = Delaunay.Voronoi(delaunay);
// TODO: We expect 8 because it currently outputs the lines repeated twice.
Assert.True(voronoi.Count == 8);
}

private List<DelaunayPoint> GeneratePoints(int amout, double maxX, double maxY, out List<DelaunayTriangle> border)
{

DelaunayPoint point0 = new DelaunayPoint(0, 0);
DelaunayPoint point1 = new DelaunayPoint(0, maxY);
DelaunayPoint point2 = new DelaunayPoint(maxX, maxY);
DelaunayPoint point3 = new DelaunayPoint(maxX, 0);
List<DelaunayPoint> points = new List<DelaunayPoint>() { point0, point1, point2, point3 };
DelaunayTriangle triangle1 = new DelaunayTriangle(point0, point1, point2);
DelaunayTriangle triangle2 = new DelaunayTriangle(point0, point2, point3);
border = new List<DelaunayTriangle> { triangle1, triangle2 };
Random rnd = new Random();
List<DelaunayPoint> points2 = new List<DelaunayPoint>();
for (int i = 0; i < amout - 4; i++)
{
points2.Add(RandomPoint(rnd, 0, maxX));
}
return points2;
}


public static DelaunayPoint RandomPoint(Random RandGenerator, double MinValue, double MaxValue)
{
double range = MaxValue - MinValue;
DelaunayPoint randomPoint = new DelaunayPoint(RandGenerator.NextDouble() * range + MinValue, RandGenerator.NextDouble() * range + MinValue);
return randomPoint;
}
}
}

0 comments on commit 40478bf

Please sign in to comment.