Title: See whether a point is above an arc in C#
This example shows how you can determine which part of an arc is at a particular position. If you move the mouse over the arc's start or end point, the program changes its cursor to the PanWest and PanEast cursors. If you move the mouse over the arc's body, the program changes its cursor to the SizeAll cursor. When the mouse is not over any part of the arc, the program displays the default cursor.
The program identifies the arc's parts in two main steps. When it starts, it defines the arc and finds its end points. Later, when you move the mouse, the program determines which part, if any, is under the mouse.
Initialization
When it starts, the program uses the following code to define its arc.
// The arc's geometry.
private RectangleF ArcBounds;
private float StartAngle, SweepAngle;
private PointF[] EndPoints;
// Define the arc.
private void Form1_Load(object sender, EventArgs e)
{
// Define the arc.
ArcBounds = new RectangleF(20, 20,
picCanvas.ClientSize.Width  40,
picCanvas.ClientSize.Height  40);
StartAngle = 45;
SweepAngle = 255;
// Get the arc's end points.
EndPoints = FindArcEndPoints(ArcBounds,
StartAngle, StartAngle + SweepAngle);
}
The form's Load event handler sets the ArcBounds rectangle that defines the arc that bounds the ellipse containing the arc. It sets the StartAngle and SweepAngle to determine the arc's part of the ellipse.
The program then calls the FindArcEndPoints method to find the arc's start and end points. That method is the same as the FindEllipsePoints method described in the post Make an intuitive extension method to draw an elliptical arc in WPF and C#. See that post for a description.
Finding Arc Pieces
When you move the mouse over the program's PictureBox, the following event handler executes.
// See what's under the mouse.
private void picCanvas_MouseMove(object sender, MouseEventArgs e)
{
switch (ArcPartAtPoint(ArcBounds,
EndPoints[0], EndPoints[1], e.Location, 3))
{
case Part.None:
picCanvas.Cursor = Cursors.Default;
break;
case Part.Body:
picCanvas.Cursor = Cursors.SizeAll;
break;
case Part.StartPoint:
picCanvas.Cursor = Cursors.PanWest;
break;
case Part.EndPoint:
picCanvas.Cursor = Cursors.PanEast;
break;
default:
throw new Exception("Unknown arc part");
}
}
This code calls the ArcPartAtPoint method described next. It sets the PictureBox control's cursor appropriately for the part of the arc under the mouse.
The most interesting piece in the rest of the program is the ArcPartAtPoint method.
// Pieces of the arc that the mouse might be over.
public enum Part
{
None,
StartPoint,
EndPoint,
Body,
}
// Return the part of the arc that is at the given point.
public Part ArcPartAtPoint(RectangleF arc_bounds,
PointF start_point, PointF end_point,
Point target, float radius)
{
// See if the mouse is at the start or end point.
if (Distance(target, start_point) < radius)
return Part.StartPoint;
if (Distance(target, end_point) < radius)
return Part.EndPoint;
// Find the angle from the ellipse's center to the point.
float cx = (arc_bounds.Left + arc_bounds.Right) / 2f;
float cy = (arc_bounds.Top + arc_bounds.Bottom) / 2f;
float dx = target.X  cx;
float dy = target.Y  cy;
float radians = (float)Math.Atan2(dy, dx);
float degrees = (float)(radians * 180 / Math.PI);
while (degrees < 0) degrees += 360;
// Get the arc's minimum and maximum angles.
float min_angle = Math.Min(StartAngle, StartAngle + SweepAngle);
float max_angle = Math.Max(StartAngle, StartAngle + SweepAngle);
// Make degrees be the smallest value
// greater than or equal to min_angle.
while (degrees > min_angle) degrees = 360;
while (degrees < min_angle) degrees += 360;
// See if the angle is within the ellipse's sweep.
if ((degrees < min_angle) 
(degrees > max_angle))
return Part.None;
// Find the point on the ellipse at this angle.
PointF center = new PointF(cx, cy);
PointF[] intersections =
FindEllipseSegmentIntersections(
arc_bounds, center, target, false);
for (int i = 0; i < intersections.Length; i++)
if (Distance(target, intersections[i]) < radius)
return Part.Body;
// It's not on the arc.
return Part.None;
}
This code first defines the Part enumeration to identify the parts of an arc.
The ArcPartAtPoint method first uses the Distance helper method to find the distance between the target point and the arc's end points. If the distance is less than the desired radius, then the method returns a value indicating which end point is at the target point. (The Distance method is straightforward so it isn't shown here.)
Next, the method finds the angle from the arc's center to the target point. If that angle is not between the arc's start angle and its end angle, the method returns Part.None to indicate that the target point does not lie on the ellipse. (Note that this assumes that the start angle lies between 0 and 360 degrees.)
The method the uses the FindEllipseSegmentIntersections method to find the points where the line between the arc's center and the target point intersect the arc's ellipse. If one of those points of intersection lies within distance radius of the target point, then the target point lies on (or at least very close to) the arc's body, so the method returns Part.Body.
Note that the point of intersection may not be the point on the arc that is closest to the target point. Unless the arc is perpendicular to the line, the closest point will be some other nearby point. However, if the radius we are using to test for closeness is reasonably small, then the result should be reasonably accurate. Let me know if you find examples where that isn't true.
In my next few posts, I'll explain how you can let the user draw and modify arcs.
Download the example to experiment with it and to see additional details.
