Getting the endpoint in ArcSegment using Start X / Y and Start + Sweep Angles

Does anyone have a good ArcSegment endpoint calculation algorithm? This is not a circular arc - it is elliptical.

For example, I have these initial values:

  • Starting point X = 0.251
  • Starting point Y = 0.928
  • Width Radius = 0.436
  • Growth Radius = 0.593
  • Initial angle = 169.51
  • Sweep Angle = 123.78

I know the location where my arc ends, to the right of X = 0.92 and Y = 0.33 (through another program), but I need to do this in ArcSegment with an endpoint. I just need to know how to calculate the endpoint so that it looks like this:

 <ArcSegment Size="0.436,0.593" Point="0.92,0.33" IsLargeArc="False" SweepDirection="Clockwise" /> 

Does anyone know a good way to calculate this? (I do not think it is important that this is WPF or any other language, since the math should be the same).

Here is the image. All values ​​are known in it, except for the endpoint (orange dot). image depicting arc


EDIT: I found that there is a DrawArc subroutine with overloading in .NET GDI + that pretty much does what I need (more by "pretty much" per second).

To make browsing easier, follow these steps:

 Public Sub MyDrawArc(e As PaintEventArgs) Dim blackPen As New Pen(Color.Black, 2) Dim x As Single = 0.0F Dim y As Single = 0.0F Dim width As Single = 100.0F Dim height As Single = 200.0F Dim startAngle As Single = 180.0F Dim sweepAngle As Single = 135.0F e.Graphics.DrawArc(blackPen, x, y, width, height, startAngle, sweepAngle) Dim redPen As New Pen(Color.Red, 2) e.Graphics.DrawLine(redPen, New Point(0, 55), New Point(95, 55)) End Sub Private Sub ImageBox_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles ImageBox.Paint MyDrawArc(e) End Sub 

This procedure directly puts the endpoint at X=95, Y=55 . Other routines mentioned for circular ellipses will result in X=85, Y=29 . If there was a way 1) You do not need to draw anything, but 2) have e.Graphics.DrawArc return the coordinates of the end point, this is what I need.

So, now the question is getting some clarity - does anyone know how e.Graphics.DrawArc is implemented?

+6
geometry wpf drawing geometric-arc
source share
3 answers

Does anyone know how e.Graphics.DrawArc is implemented?

Graphics.DrawArc calls the built-in function GdipDrawArcI in gdiplus.dll. This function calls the arc2polybezier function in the same DLL. Apparently, the Bezier curve is used to approximate the elliptical arc. To get the same endpoint that you are looking for, we would have to reconstruct this function and find out how it works.

Fortunately, the good people at Wine have already done for us .

Here is the arc2polybezier method, roughly translated from C to C # (note that since it was translated from Wine, this code is licensed under LGPL ):

 internal class GdiPlus { public const int MAX_ARC_PTS = 13; public static int arc2polybezier(Point[] points, double x1, double y1, double x2, double y2, double startAngle, double sweepAngle) { int i; double end_angle, start_angle, endAngle; endAngle = startAngle + sweepAngle; unstretch_angle(ref startAngle, x2/2.0, y2/2.0); unstretch_angle(ref endAngle, x2/2.0, y2/2.0); /* start_angle and end_angle are the iterative variables */ start_angle = startAngle; for(i = 0; i < MAX_ARC_PTS - 1; i += 3) { /* check if we've overshot the end angle */ if(sweepAngle > 0.0) { if(start_angle >= endAngle) break; end_angle = Math.Min(start_angle + Math.PI/2, endAngle); } else { if(start_angle <= endAngle) break; end_angle = Math.Max(start_angle - Math.PI/2, endAngle); } if(points != null) { Point[] returnedPoints = add_arc_part(x1, y1, x2, y2, start_angle, end_angle, i == 0); //add_arc_part returns a Point[] of size 4 for(int j = 0; j < 4; j++) points[i + j] = returnedPoints[j]; } start_angle += Math.PI/2*(sweepAngle < 0.0 ? -1.0 : 1.0); } if(i == 0) return 0; return i + 1; } public static void unstretch_angle(ref double angle, double rad_x, double rad_y) { angle = deg2rad(angle); if(Math.Abs(Math.Cos(angle)) < 0.00001 || Math.Abs(Math.Sin(angle)) < 0.00001) return; double stretched = Math.Atan2(Math.Sin(angle)/Math.Abs(rad_y), Math.Cos(angle)/Math.Abs(rad_x)); int revs_off = (int)Math.Round(angle/(2.0*Math.PI), MidpointRounding.AwayFromZero) - (int)Math.Round(stretched/(2.0*Math.PI), MidpointRounding.AwayFromZero); stretched += revs_off*Math.PI*2.0; angle = stretched; } public static double deg2rad(double degrees) { return Math.PI*degrees/180.0; } private static Point[] add_arc_part(double x1, double y1, double x2, double y2, double start, double end, bool write_first) { double center_x, center_y, rad_x, rad_y, cos_start, cos_end, sin_start, sin_end, a, half; int i; rad_x = x2/2.0; rad_y = y2/2.0; center_x = x1 + rad_x; center_y = y1 + rad_y; cos_start = Math.Cos(start); cos_end = Math.Cos(end); sin_start = Math.Sin(start); sin_end = Math.Sin(end); half = (end - start)/2.0; a = 4.0/3.0*(1 - Math.Cos(half))/Math.Sin(half); Point[] pt = new Point[4]; if(write_first) { pt[0].X = cos_start; pt[0].Y = sin_start; } pt[1].X = cos_start - a*sin_start; pt[1].Y = sin_start + a*cos_start; pt[3].X = cos_end; pt[3].Y = sin_end; pt[2].X = cos_end + a*sin_end; pt[2].Y = sin_end - a*cos_end; /* expand the points back from the unit circle to the ellipse */ for(i = (write_first ? 0 : 1); i < 4; i ++) { pt[i].X = pt[i].X*rad_x + center_x; pt[i].Y = pt[i].Y*rad_y + center_y; } return pt; } } 

Using this code as a guide, as well as some math, I wrote this class of endpoint calculator (not LGPL):

 using System; using System.Windows; internal class DrawArcEndPointCalculator { public Point GetFinalPoint(Point startPoint, double width, double height, double startAngle, double sweepAngle) { Point radius = new Point(width / 2.0, height / 2.0); double endAngle = startAngle + sweepAngle; int sweepDirection = (sweepAngle < 0 ? -1 : 1); //Adjust the angles for the radius width/height startAngle = UnstretchAngle(startAngle, radius); endAngle = UnstretchAngle(endAngle, radius); //Determine how many times to add the sweep-angle to the start-angle int angleMultiplier = (int)Math.Floor(2*sweepDirection*(endAngle - startAngle)/Math.PI) + 1; angleMultiplier = Math.Min(angleMultiplier, 4); //Calculate the final resulting angle after sweeping double calculatedEndAngle = startAngle + angleMultiplier*Math.PI/2*sweepDirection; calculatedEndAngle = sweepDirection*Math.Min(sweepDirection * calculatedEndAngle, sweepDirection * endAngle); //Calculate the final point return new Point { X = (Math.Cos(calculatedEndAngle) + 1)*radius.X + startPoint.X, Y = (Math.Sin(calculatedEndAngle) + 1)*radius.Y + startPoint.Y, }; } private double UnstretchAngle(double angle, Point radius) { double radians = Math.PI * angle / 180.0; if(Math.Abs(Math.Cos(radians)) < 0.00001 || Math.Abs(Math.Sin(radians)) < 0.00001) return radians; double stretchedAngle = Math.Atan2(Math.Sin(radians) / Math.Abs(radius.Y), Math.Cos(radians) / Math.Abs(radius.X)); int rotationOffset = (int)Math.Round(radians / (2.0 * Math.PI), MidpointRounding.AwayFromZero) - (int)Math.Round(stretchedAngle / (2.0 * Math.PI), MidpointRounding.AwayFromZero); return stretchedAngle + rotationOffset * Math.PI * 2.0; } } 

Here are some examples. Please note that the first example you cited is incorrect - for these initial values DrawArc() will have an endpoint (0.58, 0.97), not (0.92, 0.33).

 Point startPoint = new Point(0, 0); double width = 100; double height = 200; double startAngle = 180; double sweepAngle = 135; DrawArcEndPointCalculator _endPointCalculator = new DrawArcEndPointCalculator(); Point lastPoint = _endPointCalculator.GetFinalPoint(startPoint, width, height, startAngle, sweepAngle); Console.WriteLine("X = {0}, Y = {1}", lastPoint.X, lastPoint.Y); //Output: X = 94.7213595499958, Y = 55.2786404500042 startPoint = new Point(0.251, 0.928); width = 0.436; height = 0.593; startAngle = 169.51; sweepAngle = 123.78; _endPointCalculator.GetFinalPoint(startPoint, width, height, startAngle, sweepAngle); //Returns X = 0.579143189905416, Y = 0.968627455618129 Point startPoint = new Point(0, 0); double width = 20; double height = 30; double startAngle = 90; double sweepAngle = 90; _endPointCalculator.GetFinalPoint(startPoint, width, height, startAngle, sweepAngle); //Returns X = 0, Y = 15 
+10
source share
 1) Given this: xStart = .25 yStart = .92 startAngle = 169.51 sweepAngle = 123.78 Rx = .436 // this is radius width Ry = .593 // this is radius height 2) Calculations: centerX = xStart - Rx * cos(startAngle) centerY = yStart - Ry * sin(startAngle) endAngle = startAngle + sweepAngle xEnd = centerX + Rx * cos(endAngle) yEnd = centerY + Ry * sin(endAngle) 

So your coordinate is (xEnd, yEnd).

+2
source share

Did it help:
Math ArcSegment

+1
source share

All Articles