using System;
using System.Collections.Generic;
using System.Windows;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SketchAssistantWPF
{
///
/// A class that contains all algorithms related to geometry.
///
public static class GeometryCalculator
{
///
/// Calculate the approximate similarity of two lines.
/// Using three weighted parameters to calculate a value between 0 and 1 to indicate the similarity of the lines.
///
/// The first line.
/// The second line.
/// The similarity of the two lines.
public static double CalculateSimilarity(InternalLine line0, InternalLine line1)
{
double CosSim = Math.Abs(CalculateAverageCosineSimilarity(line0, line1));
double LenSim = CalculateLengthSimilarity(line0, line1);
double AvDist = CalculateAverageDistance(line0, line1);
double DistSim = (50 - AvDist) / 50;
if (DistSim < 0) DistSim = 0;
if (CosSim < 0.5 || Double.IsNaN(CosSim)) CosSim = 0;
double output = (2 * CosSim + LenSim + DistSim) / 4;
System.Diagnostics.Debug.WriteLine("Results: CosSim: {0}, LenSim: {1}, AvDist {2}, DistSim: {3}. Total: {4}",
CosSim, LenSim, AvDist, DistSim, output);
return output;
}
///
/// The cosine similarity of two vectors.
///
/// The first vector
/// The second vector
/// The cosine similarity
private static double CosineSimilarity(Vector v0, Vector v1)
{
return (v0.X * v1.X + v0.Y * v1.Y) / (Math.Sqrt(v0.X * v0.X + v0.Y * v0.Y) * Math.Sqrt(v1.X * v1.X + v1.Y * v1.Y));
}
///
/// An approximate calculation of the average cosine similarity
/// of two lines, achieved by calculating the cosine similarity of subvectors.
///
/// The first line
/// The second line
/// The approximate average cosine similarity of all subvectors
public static double CalculateAverageCosineSimilarity(InternalLine line0, InternalLine line1)
{
//check if one of the lines is a point, or both lines are points
if ((line0.isPoint && !line1.isPoint) || (line1.isPoint && !line0.isPoint)) return 0;
else if (line0.isPoint && line1.isPoint) return 1;
List points0 = line0.GetPoints();
List points1 = line1.GetPoints();
if (points0.Count == points1.Count)
{
//If the two lists have an equal amount of subvectors,
//compare the nth subvectors from each list and average the value.
double sum = 0; int i = 0;
List shortL = points0; List longL = points1;
for (; i < shortL.Count - 1; i++)
{
if (i + 1 == shortL.Count || i + 1 == longL.Count) break;
Vector v0 = new Vector(shortL[i + 1].X - shortL[i].X, shortL[i + 1].Y - shortL[i].Y);
Vector v1 = new Vector(longL[i + 1].X - longL[i].X, longL[i + 1].Y - longL[i].Y);
sum += CosineSimilarity(v0, v1);
}
return sum / i;
}
else
{
//Determine if the longer list is of similar length or contains significatly more items
List shortL = points0; List longL = points0;
if (points0.Count < points1.Count) { longL = points1; }
if (points0.Count > points1.Count) { shortL = points1;}
double dif = (longL.Count - 1) / (shortL.Count - 1);
if(dif > 1)
{
//The longer list is significantly longer
//Each element in the shorter list is compared to multiple
// elements in the longer list to make up the difference
double sum = 0; int adds = 0;
for (int i = 0; i < shortL.Count - 1; i++)
{
if (i + 1 == shortL.Count) break;
for (int j = 0; j <= dif; j++)
{
var k = i + j;
if (k + 1 == longL.Count) break;
Vector v0 = new Vector(shortL[i + 1].X - shortL[i].X, shortL[i + 1].Y - shortL[i].Y);
Vector v1 = new Vector(longL[k + 1].X - longL[k].X, longL[k + 1].Y - longL[k].Y);
sum += CosineSimilarity(v0, v1); adds++;
}
}
return sum / adds;
}
else
{
//The longer list is almost the same length as the shorter list
//The remaining items are simply skipped
double sum = 0; int i = 0;
for (; i < shortL.Count - 1; i++)
{
if (i + 1 == shortL.Count || i + 1 == longL.Count) break;
Vector v0 = new Vector(shortL[i + 1].X - shortL[i].X, shortL[i + 1].Y - shortL[i].Y);
Vector v1 = new Vector(longL[i + 1].X - longL[i].X, longL[i + 1].Y - longL[i].Y);
sum += CosineSimilarity(v0, v1);
}
return sum / i;
}
}
}
///
/// Calculate the similarity in length of two Lines.
///
/// The first line.
/// The second line.
/// How similar the lines are in length.
private static double CalculateLengthSimilarity(InternalLine line0, InternalLine line1)
{
double len0 = line0.GetLength(); double len1 = line1.GetLength();
var dif = Math.Abs(len1 - len0);
if (dif == 0) return 1;
double shorter;
if (len1 > len0) shorter = len0;
else shorter = len1;
if (dif >= shorter) return 0;
return (shorter - dif )/shorter;
}
///
/// Calculate the average distance between the ends of two lines.
///
/// The first line.
/// The second line.
/// The shortest average distance between the ends of the lines.
private static double CalculateAverageDistance(InternalLine line0, InternalLine line1)
{
List points0 = line0.GetPoints();
List points1 = line1.GetPoints();
double distfirstfirst = Math.Sqrt(Math.Pow((points0[0].X - points1[0].X) , 2) + Math.Pow((points0[0].Y - points1[0].Y) , 2));
double distlastlast = Math.Sqrt(Math.Pow((points0.Last().X - points1.Last().X), 2) + Math.Pow((points0.Last().Y - points1.Last().Y), 2));
double distfirstlast = Math.Sqrt(Math.Pow((points0[0].X - points1.Last().X), 2) + Math.Pow((points0[0].Y - points1.Last().Y), 2));
double distlastfirst = Math.Sqrt(Math.Pow((points0.Last().X - points1[0].X), 2) + Math.Pow((points0.Last().Y - points1[0].Y), 2));
if ((distfirstfirst + distlastlast) / 2 < (distfirstlast + distlastfirst) / 2) return (distfirstfirst + distlastlast) / 2;
else return (distfirstlast + distlastfirst) / 2;
}
///
/// A simple algorithm that returns a filled circle with a radius and a center point.
///
/// The center point of the alorithm
/// The radius of the circle, if its less or equal to 1
/// only the center point is returned.
/// All the points in or on the circle.
public static HashSet FilledCircleAlgorithm(Point center, int radius)
{
HashSet returnSet = new HashSet { center };
//Fill the circle
for (int x = 0; x < radius; x++)
{
for (int y = 0; y < radius; y++)
{
//Check if point is on or in the circle
if ((x * x + y * y - radius * radius) <= 0)
{
returnSet.Add(new Point(center.X + x, center.Y + y));
returnSet.Add(new Point(center.X - x, center.Y + y));
returnSet.Add(new Point(center.X + x, center.Y - y));
returnSet.Add(new Point(center.X - x, center.Y - y));
}
}
}
return returnSet;
}
///
/// An implementation of the Bresenham Line Algorithm,
/// which calculates all points between two points in a straight line.
/// Implemented using the pseudocode on Wikipedia.
///
/// The start point
/// The end point
/// All points between p0 and p1 (including p0 and p1)
public static List BresenhamLineAlgorithm(Point p0, Point p1)
{
int p1x = (int)p1.X;
int p1y = (int)p1.Y;
int p0x = (int)p0.X;
int p0y = (int)p0.Y;
int deltaX = p1x - p0x;
int deltaY = p1y - p0y;
List returnList;
if (Math.Abs(deltaY) < Math.Abs(deltaX))
{
if (p0.X > p1.X)
{
returnList = GetLineLow(p1x, p1y, p0x, p0y);
returnList.Reverse();
}
else
{
returnList = GetLineLow(p0x, p0y, p1x, p1y);
}
}
else
{
if (p0.Y > p1.Y)
{
returnList = GetLineHigh(p1x, p1y, p0x, p0y);
returnList.Reverse();
}
else
{
returnList = GetLineHigh(p0x, p0y, p1x, p1y);
}
}
return returnList;
}
///
/// Helping function of the Bresenham Line algorithm,
/// under the assumption that abs(deltaY) is smaller than abs(deltX)
/// and x0 is smaller than x1
///
/// x value of point 0
/// y value of point 0
/// x value of point 1
/// y value of point 1
/// All points on the line between the two points
private static List GetLineLow(int x0, int y0, int x1, int y1)
{
List returnList = new List();
int dx = x1 - x0;
int dy = y1 - y0;
int yi = 1;
if (dy < 0)
{
yi = -1;
dy = -dy;
}
int D = 2 * dy - dx;
int y = y0;
for (int x = x0; x <= x1; x++)
{
returnList.Add(new Point(x, y));
if (D > 0)
{
y = y + yi;
D = D - 2 * dx;
}
D = D + 2 * dy;
}
return returnList;
}
///
/// Helping function of the Bresenham Line algorithm,
/// under the assumption that abs(deltaY) is larger or equal than abs(deltX)
/// and y0 is smaller than y1
///
/// x value of point 0
/// y value of point 0
/// x value of point 1
/// y value of point 1
/// All points on the line between the two points
private static List GetLineHigh(int x0, int y0, int x1, int y1)
{
List returnList = new List();
int dx = x1 - x0;
int dy = y1 - y0;
int xi = 1;
if (dx < 0)
{
xi = -1;
dx = -dx;
}
int D = 2 * dx - dy;
int x = x0;
for (int y = y0; y <= y1; y++)
{
returnList.Add(new Point(x, y));
if (D > 0)
{
x = x + xi;
D = D - 2 * dy;
}
D = D + 2 * dx;
}
return returnList;
}
}
}