简体   繁体   中英

Drawing a 3-Point Arc VB.NET

I'm trying to create a simple interface (like paint) to draw a few basic shapes such as lines, circles, and arcs. I've already got lines and circles figured out, but I'm having difficulties getting the desired effect of drawing arcs. I'm using the graphics.Draw... methods in .NET right now, but might be willing to try a different native approach.

I'm looking to create the functionality that a user picks a start point, end point and "radius". However, I want the "semi-circle" to be touching all three points. 在此处输入图片说明 Following the picture (taken from the application), Pt5 is calculated from the user's three clicks and sets the "radius" of the arc. The image depicts that the center of the circle is on Pt4 , but ideally the center would be calculated with Pt1 and Pt2 at the edge.

Once the center is calculated, I'd like to use it to create the Arc's bounds ( DrawArc(Pens.Black, CInt(Center.X - Radius), CInt(Center.Y - Radius), CInt(Radius * 2), CInt(Radius * 2), ?, ?) ) and "cut" the circle by the projection of points Pt1 and Pt2 along the Pt1-Pt2 line.

Here's my calculation for the lines/circle above (sorry for the lengthy question...):

    '' Pt1 and Pt2 are taken earlier
    Dim pt3 As Point = e.Location
    Dim pt4, pt5, pt10, pt11, Cntr As New Point
    Dim m1, m2, m3, m4 As Double
    Dim b1, b2, b3, b4, b5 As Double
    Dim r As Double
    '' Get center (midpoint)
    pt4.X = ((pt1.X - pt2.X) / 2) + pt2.X
    pt4.Y = ((pt1.Y - pt2.Y) / 2) + pt2.Y
    '' Get picked-point slope
    m1 = (pt2.Y - pt1.Y) / (pt2.X - pt1.X)
    '' Inverse slope
    m2 = -(1 / m1)
    '' Get picked-intercept
    b1 = pt1.Y - (m1 * pt1.X)

    '' Get perpendicular intercept
    b2 = pt4.Y - (m2 * pt4.X)
    '' Get parallel intercept
    b3 = pt3.Y - (m1 * pt3.X)

    ''ln1: y = m1X + b1 ; (pt1, pt2)
    ''ln2: y = m2X + b2 ; (pt4, pt5)
    ''ln3: y = m1X + b3 ; (pt3, pt5)
    '' pt5.X = (yInt1 - yInt2)/(slope1-slope2)
    '' pt5.Y = (slope2 * pt5.X) + yInt2
    pt5.X = ((b2 - b3) / (m1 - m2))
    pt5.Y = (m2 * pt5.X) + b2

    '' Get perpendicular slope between Pt1 and Pt5
    m3 = -(1 / (pt5.Y - pt1.Y) / (pt5.X - pt1.X))
    '' Get perpendicular slope between Pt2 and Pt5
    m4 = -(1 / ((pt5.Y - pt2.Y) / (pt5.X - pt2.X)))
    '' Get perpendicular intercept between Pt1 and Pt5
    b4 = pt1.Y - (m3 * pt1.X)
    '' Get perpendicular intercept between Pt2 and Pt5
    b5 = pt2.Y - (m4 * pt2.X)
    Cntr.X = (b5 - b4) / (m3 - m4) '((m3 * m4 * ((pt1.Y - pt2.Y))) + (m4 * (pt1.X + pt5.X)) - (m3 * (pt5.X + pt2.X))) / (2 * (m4 - m3))

    Cntr.Y = (m2 * Cntr.X) + b2
    '' Calculate radius
    r = Math.Sqrt(((pt5.Y - Cntr.Y) ^ 2) + ((pt5.X - Cntr.X) ^ 2))

So we're all on the same page, the DrawArc method has several overloads, but I'll be using this one

Public Sub DrawArc(pen As System.Drawing.Pen, 
                   x As Single, 
                   y As Single, 
                   width As Single, 
                   height As Single, 
                   startAngle As Single,
                   sweepAngle As Single)

In order to use it we need to calculate the bounding rectangle that contains the arc, if it were drawn as a complete ellipse, the angle to start sweeping from, and the central angle of the arc.

For the following the user is defining the three arc points in a more common way: Center, Start, Angle. That is, the first click defines the center, the second click determines the start point, and the third click determines the angle that the arc spans. It's easy enough to adapt this to other methods, but the one I'm using has the simplest math to explain.

On the first click, we record the mouse position into the variable center . This is now the center of our arc.

On the second click, we record the mouse position into the variable start . This point gives us two key pieces of information, the radius, and angle to the start point. We calculate the radius using the Pythagorean theorem (aka the distance formula) as follows:

r = Math.Sqrt(Math.Pow(start.X - center.X, 2) + Math.Pow(start.Y - center.Y))

To calculate the angle we can use the arc-tangent of Δy/Δx

theta = Math.Atan((start.Y - center.Y) / (start.X - center.X))

(I'll leave it as an exercise for you to check for (start.X - center.X) = 0 and figure out whether it's +/- π/2.)

We now also have enough information to calculate the bounding rectangle for the arc. Since the arc is circular, our box is just

x = center.X - r
y = center.Y - r
width = 2 * r
height = 2 * r

When the user clicks for the third time (or indeed whenever the mouse is moved) we store the value into the angle variable. Now, all we need to do is calculate the angle for that point, since we're drawing a circular arc segment. The second angle is calculated the same way as the previous:

alpha = Math.Atan((angle.Y - center.Y) / (angle.X - center.X))

(Remember, as before, to check for (angle.X - center.X) = 0 )

We can now calculate the sweep angle by subtracting

sweep = theta - alpha

We now have enough information to call the DrawArc method using our calculated parameters:

g.DrawArc(Pen.Black, x, y, width, height, theta, sweep)

A few final thoughts:

  • This method will always draw the arc counter-clockwise from the starting angle.
  • The center , start and end variables are all PointF , you will need to convert as necessary. I removed the conversions and capture logic to make the code above easier to follow.
  • All other variables are Single . You'll need to convert the results of the Atan function to Single .
  • If Δx is 0 when calculating the arc-tangent, you can tell whether the angle is positive or negative by comparing the y values. If (center.Y < start.Y) then you know it's +π/2. -You should also probably check for a 0 radius.

I may have misunderstood exactly what you are trying to do, but assuming that your calculation for pt5 is what you want and you want pt1 and pt2 to be on the circumference, then something like this might do it.

I think you want pt5 to be the centre.

r = Math.Sqrt((pt5.Y - pt1.Y) ^ 2 + (pt5.X - pt1.X) ^ 2)

Assuming you already have access to an appropriate graphics object, g

g.DrawEllipse(Pens.Blue, New Rectangle(pt5.X - r, pt5.Y - r, 2 * r, 2 * r))

I used a number of resources to accomplish my objective. In the end, I ended up dropping the use of DrawArc for ExcludeClip thanks to this question. Basically, once I created the right ellipse, I needed to clip the area of the ellipse between points 1 and 2. Below, you'll see my solution. The first image shows the "helper" lines I used to verify my math was correct, followed by the end result.

Legend:

*Yellow Line1: Perpendicular to Pt1-Pt2 line at midpoint (Pt4-Pt5)

*Yellow Line2: Perpendicular line to first yellow (parallel to Pt1-Pt2 line) (Pt3-Pt5)

*Pt5: Intersection point between yellow lines

*Red Line: Pt1-Pt5

*Blue Line:Pt2-Pt5

*Pink lines: Perpendicular lines to Red/Blue at midpoints (Pt10,Pt11)

*Center: Intersection point between pink lines (Cntr)

*Beige Lines: Extensions of Red/Blue lines, used in ExcludeClip {(Pt20-Pt1-Pt5) and (Pt21-Pt2-Pt5)}

带辅助线 这是用户会看到的

Here's my code:

Dim pt3 As Point = e.Location
Dim pt4, pt5, pt10, pt11, pt20, pt21, Cntr As New Point
Dim m1, m2, mr, mt As Double
Dim b1, b2, b3, b4, b5 As Double
Dim r As Double

'' Get center (midpoint)
pt4.X = ((pt1.X - pt2.X) / 2) + pt2.X
pt4.Y = ((pt1.Y - pt2.Y) / 2) + pt2.Y
'' Get picked-point slope
m1 = (pt2.Y - pt1.Y) / (pt2.X - pt1.X)
'' Inverse slope
m2 = -(1 / m1)
'' Get picked-intercept
b1 = pt1.Y - (m1 * pt1.X)
'' Get perpendicular intercept
b2 = pt4.Y - (m2 * pt4.X)
'' Get parallel intercept
b3 = pt3.Y - (m1 * pt3.X)

''ln1: y = m1X + b1 ; (pt1, pt2)
''ln2: y = m2X + b2 ; (pt4, pt5)
''ln3: y = m1X + b3 ; (pt3, pt5)
'' pt5.X = (yInt1 - yInt2)/(slope1-slope2)
'' pt5.Y = (slope2 * pt5.X) + yInt2
pt5.X = ((b2 - b3) / (m1 - m2))
pt5.Y = (m2 * pt5.X) + b2

'' Setup Pt1-Pt5 perpendicular line (Pt10-Cntr)
pt10.X = ((pt5.X - pt1.X) / 2) + pt1.X
pt10.Y = ((pt5.Y - pt1.Y) / 2) + pt1.Y


'' Setup Pt2-Pt5 perpendicular line (Pt11-Cntr)
pt11.X = ((pt5.X - pt2.X) / 2) + pt2.X
pt11.Y = ((pt5.Y - pt2.Y) / 2) + pt2.Y


'' Get perpendicular slope between Pt1 and Pt5
mr = (pt5.Y - pt1.Y) / (pt5.X - pt1.X)
'' Get perpendicular slope between Pt2 and Pt5
mt = (pt2.Y - pt5.Y) / (pt2.X - pt5.X)
'' Get perpendicular intercept between Pt1 and Pt5
b4 = pt1.Y - (mr * pt1.X)
'' Get perpendicular intercept between Pt2 and Pt5
b5 = pt2.Y - (mt * pt2.X)


If Not mr > 10000 And Not mt > 10000 And Not mr < -10000 And Not mt < -10000 And Not mr = 0 And Not mt = 0 And Not mr = mt Then
    Cntr.X = (((mr * mt) * ((pt2.Y - pt1.Y))) + (mr * (pt5.X + pt2.X)) - (mt * (pt1.X + pt5.X))) / (2 * (mr - mt))

    Cntr.Y = -(1 / mr) * (Cntr.X - ((pt1.X + pt5.X) / 2)) + ((pt1.Y + pt5.Y) / 2)
    '' Calculate radius
    r = Math.Sqrt(((pt5.Y - Cntr.Y) ^ 2) + ((pt5.X - Cntr.X) ^ 2))

    '' Determine which side to extend Chords
    If pt1.X > pt5.X Then
        pt20.X = pt1.X + (r * 2)
    Else
        pt20.X = pt1.X - (r * 2)
    End If
    pt20.Y = (mr * (pt20.X)) + b4

    If pt2.X > pt5.X Then
        pt21.X = pt2.X + (r * 2)
    Else
        pt21.X = pt2.X - (r * 2)
    End If
    pt21.Y = (mt * (pt21.X)) + b5

    'g.DrawLine(Pens.Black, pt1, pt2)
    'g.DrawLine(Pens.Orange, pt4, pt5)
    'g.DrawLine(Pens.Orange, pt3, pt5)
    'g.DrawLine(Pens.Pink, pt10, Cntr)
    'g.DrawLine(Pens.Pink, pt11, Cntr)
    'g.DrawLine(Pens.Red, pt1, pt5)
    'g.DrawLine(Pens.Blue, pt2, pt5)
    'g.DrawLine(Pens.Beige, pt1, pt20)
    'g.DrawLine(Pens.Beige, pt2, pt21)
    Dim path As New Drawing2D.GraphicsPath()
    path.AddPolygon({pt20, pt1, Cntr, pt2, pt21})
    g.ExcludeClip(New Region(path))

    g.DrawEllipse(Pens.Black, CInt(Cntr.X - r), CInt(Cntr.Y - r), CInt(r * 2), CInt(r * 2))
Else
    Debug.WriteLine("mr: " & mr.ToString & vbTab & "mt: " & mt.ToString)
End If

This helped me find the center of the circle based on 3 points on the circumference

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM