public void Test_Includeness() { var hash = new HashSet <(int, int)>(); for (var numberOfTests = 0; numberOfTests < 10; ++numberOfTests) { var numberOfPoints = 20 + numberOfTests * 10; var arr = new PointD2DAnnotated[numberOfPoints]; hash.Clear(); for (var i = 0; i < numberOfPoints;) { var x = _random.Next(-1000, 1000); var y = _random.Next(-1000, 1000); if (!hash.Contains((x, y))) { hash.Add((x, y)); arr[i] = new PointD2DAnnotated(x, y, i); ++i; } } for (double concaveness = 1; concaveness >= -1; concaveness -= 1 / 16.0) { var concaveCalc = new ConcaveHull(arr, concaveness, 100, true); IncludenessTest(concaveCalc.ConvexHullPoints, arr); IncludenessTest(concaveCalc.ConcaveHullPoints, arr); } } }
public static double GetDistance(PointD2DAnnotated node1, PointD2DAnnotated node2) { var dx = node1.X - node2.X; var dy = node1.Y - node2.Y; return(Math.Sqrt(dx * dx + dy * dy)); }
public void Test_Includeness() { var hash = new HashSet <(int, int)>(); for (var numberOfTests = 0; numberOfTests < 100; ++numberOfTests) { var numberOfPoints = 20 + numberOfTests * 10; var arr = new PointD2DAnnotated[numberOfPoints]; hash.Clear(); for (var i = 0; i < numberOfPoints;) { var x = _random.Next(-1000, 1000); var y = _random.Next(-1000, 1000); if (!hash.Contains((x, y))) { hash.Add((x, y)); arr[i] = new PointD2DAnnotated(x, y, i); ++i; } } var convexHull = GrahamScan.GetConvexHull(arr); IncludenessTest(convexHull, arr); } }
private static double GetAngle(PointD2DAnnotated p1, PointD2DAnnotated p2) { var xDiff = p2.X - p1.X; var yDiff = p2.Y - p1.Y; return(Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI); }
private static double getCos(PointD2DAnnotated a, PointD2DAnnotated b, PointD2DAnnotated o) { /* Law of cosines */ var aPow2 = Pow2(a.X - o.X) + Pow2(a.Y - o.Y); var bPow2 = Pow2(b.X - o.X) + Pow2(b.Y - o.Y); var cPow2 = Pow2(a.X - b.X) + Pow2(a.Y - b.Y); return(Math.Round((aPow2 + bPow2 - cPow2) / (2 * Math.Sqrt(aPow2 * bPow2)), 4)); }
private static void KeepLeft(List <PointD2DAnnotated> hull, PointD2DAnnotated r) { while (hull.Count > 1 && Turn(hull[hull.Count - 2], hull[hull.Count - 1], r) != TURN_LEFT) { hull.RemoveAt(hull.Count - 1); } if (hull.Count == 0 || hull[hull.Count - 1].ID != r.ID) { hull.Add(r); } }
/// <summary> /// Gets the convex hull of a set of points /// </summary> /// <param name="points">The points.</param> /// <returns>The ordered set of points that forms the hull.</returns> public static IReadOnlyList <PointD2DAnnotated> GetConvexHull(IEnumerable <PointD2DAnnotated> points) { PointD2DAnnotated p0 = default; var is_p0_initialized = false; foreach (var value in points) { if (!is_p0_initialized) { p0 = value; is_p0_initialized = true; } else { if (p0.Y > value.Y) { p0 = value; } } } if (!is_p0_initialized) { throw new ArgumentException("Enumeration is empty", nameof(points)); } var order = new List <PointD2DAnnotated>(); foreach (var value in points) { if (p0.ID != value.ID) { order.Add(value); } } order = MergeSort(p0, order); var result = new List <PointD2DAnnotated> { p0, order[0], order[1] }; order.RemoveAt(0); order.RemoveAt(0); foreach (var value in order) { KeepLeft(result, value); } return(result); }
private static bool tangentToHull(LineD2DAnnotated line_treated, PointD2DAnnotated node, double cos1, double cos2, List <LineD2DAnnotated> concave_hull) { /* A new middlepoint could (rarely) make a segment that's tangent to the hull. * This method detects these situations * I suggest turning this method of if you are not using square grids or if you have a high dot density * */ var isTangent = false; double current_cos1; double current_cos2; double edge_length; var nodes_searched = new List <int>(); LineD2DAnnotated line; PointD2DAnnotated node_in_hull; var count_line = 0; var count_node = 0; edge_length = LineD2DAnnotated.GetDistance(node, line_treated.P0) + LineD2DAnnotated.GetDistance(node, line_treated.P1); while (!isTangent && count_line < concave_hull.Count) { line = concave_hull[count_line]; while (!isTangent && count_node < 2) { node_in_hull = line[count_node]; if (!nodes_searched.Contains(node_in_hull.ID)) { if (node_in_hull.ID != line_treated.P0.ID && node_in_hull.ID != line_treated.P1.ID) { current_cos1 = getCos(node_in_hull, line_treated.P0, line_treated.P1); current_cos2 = getCos(node_in_hull, line_treated.P1, line_treated.P0); if (current_cos1 == cos1 || current_cos2 == cos2) { isTangent = (LineD2DAnnotated.GetDistance(node_in_hull, line_treated.P0) + LineD2DAnnotated.GetDistance(node_in_hull, line_treated.P1) < edge_length); } } } nodes_searched.Add(node_in_hull.ID); count_node++; } count_node = 0; count_line++; } return(isTangent); }
private static List <PointD2DAnnotated> MergeSort(PointD2DAnnotated p0, List <PointD2DAnnotated> arrPoint) { if (arrPoint.Count == 1) { return(arrPoint); } var arrSortedInt = new List <PointD2DAnnotated>(); var middle = arrPoint.Count / 2; var leftArray = arrPoint.GetRange(0, middle); var rightArray = arrPoint.GetRange(middle, arrPoint.Count - middle); leftArray = MergeSort(p0, leftArray); rightArray = MergeSort(p0, rightArray); var leftptr = 0; var rightptr = 0; for (var i = 0; i < leftArray.Count + rightArray.Count; i++) { if (leftptr == leftArray.Count) { arrSortedInt.Add(rightArray[rightptr]); rightptr++; } else if (rightptr == rightArray.Count) { arrSortedInt.Add(leftArray[leftptr]); leftptr++; } else if (GetAngle(p0, leftArray[leftptr]) < GetAngle(p0, rightArray[rightptr])) { arrSortedInt.Add(leftArray[leftptr]); leftptr++; } else { arrSortedInt.Add(rightArray[rightptr]); rightptr++; } } return(arrSortedInt); }
private static PointD2DAnnotated[] getHullNearbyNodes(LineD2DAnnotated line, List <LineD2DAnnotated> concave_hull) { /* Return previous and next nodes to a line in the hull */ var nearbyHullNodes = new PointD2DAnnotated[2]; var leftNodeID = line.P0.ID; var rightNodeID = line.P1.ID; int currentID; var nodesFound = 0; var line_count = 0; var position = 0; var opposite_position = 1; while (nodesFound < 2) { position = 0; opposite_position = 1; while (position < 2) { currentID = concave_hull[line_count][position].ID; if (currentID == leftNodeID && concave_hull[line_count][opposite_position].ID != rightNodeID) { nearbyHullNodes[0] = concave_hull[line_count][opposite_position]; nodesFound++; } else if (currentID == rightNodeID && concave_hull[line_count][opposite_position].ID != leftNodeID) { nearbyHullNodes[1] = concave_hull[line_count][opposite_position]; nodesFound++; } position++; opposite_position--; } line_count++; } return(nearbyHullNodes); }
public LineD2DAnnotated(PointD2DAnnotated p0, PointD2DAnnotated p1) { P0 = p0; P1 = p1; }
private static int Turn(PointD2DAnnotated p, PointD2DAnnotated q, PointD2DAnnotated r) { return(((q.X - p.X) * (r.Y - p.Y) - (r.X - p.X) * (q.Y - p.Y)).CompareTo(0)); }