Kelly Johnson
Kelly Johnson

Reputation: 23

Given bounding rectangle, starting angle, and sweep angle, how to determine points of the arc endpoints

Given a bounding rectangle, starting angle and the sweep angle, how do I determine the points of each end of the arc?

private void myPaint(object sender, PaintEventArgs e)   
{   
   Graphics g = e.Graphics;   

   Rectangle rc = new Rectangle(242, 299, 200, 300);   
   Pen penRed = new Pen(Color.Red, 1);   

   g.DrawArc(penRed, rc, 18, -108);   

   // TODO - Determine Point of each end of arc   
   // Point pt1 = ???
   // Point pt2 = ???
}

enter image description here

Upvotes: 2

Views: 1606

Answers (1)

theB
theB

Reputation: 6738

Using the equation of an ellipse from this excellent Mathematics answer We can calculate the start and end points of your ellipse, given the start angle and sweep.

First, we need the center of the bounding box, so we know how to shift the coordinates. That's simply

Rectangle rc = new Rectangle(242, 299, 200, 300);

int cX = (rc.Left + rc.Right) / 2;
int cY = (rc.Bottom + rc.Top) / 2;

// For debugging purposes, let's mark that point.
g.FillRectangle(Brushes.Yellow, Rectangle.FromLTRB(cX - 3, cY - 3, cX + 3, cY + 3));

We then need to convert the angles from degrees into radians, and change the clockwise angle to a counter-clockwise angle like so:

double minTheta = (Math.PI / 180) * (360 - start);
double maxTheta = (Math.PI / 180) * (360 - (start + sweep));

We'll also define 2 helper functions, the first to normalize an angle (map arbitrary angles into the range 0-360) and the second to adjust the calculated (x, y) coordinates into the correct quadrant. (Given that positive y is actually down on the form)

public double NormalizeAngle(double angle)
{
    while (angle >= 360) angle -= 360;
    while (angle < 0) angle += 360;
    return angle;
}
public void AdjustCoordinatesForAngle(double angle, ref double x, ref double y)
{
    if (angle > 0 && angle <= 90)
    {
        x *= 1;
        y *= 1;
    }
    else if (angle >= 90 && angle < 180)
    {
        x *= -1;
        y *= 1;
    }
    else if (angle >= 180 && angle < 270)
    {
        x *= -1;
        y *= -1;
    }
    else if (angle >= 270 && angle < 360)
    {
        x *= 1;
        y *= -1;
    }
}

We now have enough information to calculate the start and end points.

double minTheta = (Math.PI / 180) * (360 - start);
double maxTheta = (Math.PI / 180) * (360 - (start + sweep));

double a = width / 2.0;
double b = height / 2.0;

double denom = Math.Pow(a, 2) * Math.Pow(Math.Tan(minTheta), 2);
denom = denom / Math.Pow(b, 2);
denom = Math.Sqrt(denom + 1);

double x = Math.Abs(a / denom);
double y = Math.Abs((a * Math.Tan(minTheta)) / denom);

start = NormalizeAngle(start);
this.AdjustCoordinatesForAngle(start, ref x, ref y);

Those coordinates are relative to the bounding box's center, so we offset it using the center point we calculated above:

x += cX;
y += cY;

We can now draw the point:

g.FillRectangle(Brushes.Purple, new Rectangle((int)x - 3, (int)y - 3, 6, 6));

All together the paint function looks like this:

private void myPaint(object sender, PaintEventArgs e)
{
    double start = 18;
    double sweep = -108;

    Graphics g = e.Graphics;

    g.Clear(Color.Black);

    Rectangle rc = new Rectangle(200, 10, 200, 300);

    int cX = (rc.Left + rc.Right) / 2;
    int cY = (rc.Bottom + rc.Top) / 2;

    g.FillRectangle(Brushes.Yellow, Rectangle.FromLTRB(cX - 3, cY - 3, cX + 3, cY + 3));

    int width = rc.Width;
    int height = rc.Height;

    if (start >= 360) start -= 360;

    double minTheta = (Math.PI / 180) * (360 - start);
    double maxTheta = (Math.PI / 180) * (360 - (start + sweep));

    double a = width / 2.0;
    double b = height / 2.0;

    double denom = Math.Pow(a, 2) * Math.Pow(Math.Tan(minTheta), 2);
    denom = denom / Math.Pow(b, 2);
    denom = Math.Sqrt(denom + 1);

    double x = Math.Abs(a / denom);
    double y = Math.Abs((a * Math.Tan(minTheta)) / denom);

    start = NormalizeAngle(start);
    this.AdjustCoordinatesForAngle(start, ref x, ref y);

    x += cX;
    y += cY;

    g.FillRectangle(Brushes.Purple, new Rectangle((int)x - 3, (int)y - 3, 6, 6));

    denom = Math.Pow(a, 2) * Math.Pow(Math.Tan(maxTheta), 2);
    denom = denom / Math.Pow(b, 2);
    denom = Math.Sqrt(denom + 1);

    x = Math.Abs(a / denom);
    y = Math.Abs((a * Math.Tan(maxTheta)) / denom);

    double endAngle = (start + sweep);
    endAngle = NormalizeAngle(endAngle);
    this.AdjustCoordinatesForAngle(endAngle, ref x, ref y);

    x += cX;
    y += cY;

    g.FillRectangle(Brushes.Blue, new Rectangle((int)x - 3, (int)y - 3, 6, 6));


    Pen penRed = new Pen(Color.Red, 1);
    g.DrawRectangle(Pens.Green, rc);
    g.DrawArc(penRed, rc, (float)start, (float)sweep);
}

I painted the window background black to make the boxes and lines stand out better, and I left in some additional drawing elements, so it is easier to see what's happening in the calculations above.

Placing the code into a form, and associating with the form's paint event produces this result:

Final Result

One final note, due to rounding, the start and end points may be off by a pixel or two. If you want more accuracy, you'd have to draw the arc yourself.

Upvotes: 4

Related Questions