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; // See if the angle is within the ellipse's sweep. if ((degrees < StartAngle) || (degrees > StartAngle + SweepAngle)) 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.

Download the example to see additional details. In my next few posts, I’ll explain how you can let the user draw and modify arcs.

Pingback: Let the user draw, move, and modify an arc in C# - C# HelperC# Helper