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:
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. Single
. You'll need to convert the results of the Atan
function to Single
. (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.