/// <summary> /// Constructor for node model /// </summary> /// <param name="node1">The first of two nodes the line connects</param> /// <param name="node2">The second of two nodes that the line connects</param> /// <param name="color">The color of the line when drawn</param> public Line(Node node1, Node node2, Color color) { if (node1.X < node2.X || node1.X == node2.X && node1.Y < node2.Y) { this.node1 = node1; this.node2 = node2; } else { this.node1 = node2; this.node2 = node1; } lineColor = color; }
/// <summary> /// Calculates the angle between two nodes /// </summary> /// <param name="nodeA">Base node, assumed to be lowest of both nodes</param> /// <param name="nodeB">Measured node, should be equal to or higher in height than base node</param> /// <returns>The angle, in degrees, between the two nodes; angle is on [0, 180] degrees</returns> private double AngleBetweenPoints(Node nodeA, Node nodeB) { int yPrime = nodeA.Y - nodeB.Y; int xPrime = nodeA.X - nodeB.X; double z = Math.Sqrt(xPrime * xPrime + yPrime * yPrime); double angle = (180 / Math.PI) * Math.Asin(yPrime / z); if (xPrime == 0) { return 90; } else if (xPrime > 0) { return 180 - angle; } else { return angle; } }
/// <summary> /// Draws a single node on the graph /// </summary> /// <param name="node">The node to draw</param> private void DrawNode(Node node) { float halfRadius = radius / 2.0f; using (Graphics g = Graphics.FromImage(self)) { g.FillRectangle(new SolidBrush(node.NodeColor), node.X - halfRadius, node.Y - halfRadius, 2 * radius, 2 * radius); } }
/// <summary> /// Computes the cross product of three points /// </summary> /// <param name="p1">First node</param> /// <param name="p2">Second node</param> /// <param name="p3">Third node</param> /// <returns>Positive number if left turn, negative number if right turn, 0 if co-linear</returns> private int CrossProduct(Node p1, Node p2, Node p3) { return (p3.X - p1.X) * (p2.Y - p1.Y) - (p2.X - p1.X) * (p3.Y - p1.Y); }
/// <summary> /// Determines if the line drawn from nodeA to nodeB split the other nodes such that all other nodes fall on one side /// </summary> /// <param name="nodes">List of nodes to check</param> /// <param name="nodeA">First node of potential hull line</param> /// <param name="nodeB">Second node of potential hull line</param> /// <returns>True if line from nodeA to nodeB belongs in the Convex Hull</returns> private bool ComputeStep(List<Node> nodes, Node nodeA, Node nodeB) { bool? IsLeftTurnExpected = null; foreach (Node nodeC in nodes) { if (nodeA == nodeC || nodeB == nodeC) { continue; } int test = CrossProduct(nodeA, nodeB, nodeC); if (IsLeftTurnExpected == null) { IsLeftTurnExpected = (test < 0); } if (test == 0) { continue; } if (IsLeftTurnExpected == true && test > 0) { return false; } else if (IsLeftTurnExpected == false && test < 0) { return false; } } return true; }
/// <summary> /// Calculates and sorts a list of nodes by angel /// </summary> /// <param name="nodes">The nodes to sort</param> /// <param name="baseNode">The node to sort with respect to</param> /// <returns>A list of nodes sorted by angle from the base node counter-clockwise</returns> private List<Node> SortByAngle(List<Node> nodes, Node baseNode) { var nodeAngles = new Dictionary<Node, Double>(); foreach (Node node in nodes) { if (node == baseNode) { continue; } double angle = AngleBetweenPoints(baseNode, node); nodeAngles.Add(node, angle); } List<KeyValuePair<Node, Double>> nodeAngleList = nodeAngles.ToList(); nodeAngleList.Sort( (firstPair, secondPair) => { return firstPair.Value.CompareTo(secondPair.Value); }); List<Node> sortedNodes = new List<Node>(); foreach (KeyValuePair<Node, Double> node in nodeAngleList) { sortedNodes.Add(node.Key); } return sortedNodes; }