使用起始 X/Y 和起始扫描角度获取 ArcSegment 中的终点

发布于 2024-10-26 23:11:53 字数 1733 浏览 1 评论 0原文

有没有人有一个好的算法来计算ArcSegment的终点?这不是圆弧,而是椭圆弧。

例如,我有以下初始值:

  • 起始点 X = 0.251
  • 起始点 Y = 0.928
  • 宽度半径 = 0.436
  • 高度半径 = 0.593
  • 起始角度 = 169.51
  • 扫描角度 = 123.78

我知道我的圆弧应该结束的位置就在 X 周围=0.92 和 Y=0.33(通过另一个程序),但我需要在 ArcSegment 中执行此操作并指定终点。我只需要知道如何计算终点,所以它看起来像这样:

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

有人知道计算这个的好方法吗? (我认为这是 WPF 或任何其他语言并不重要,因为数学应该是相同的)。

这是一张图片。除了终点(橙色点)之外,所有值都是已知的。 描绘弧线的图像


编辑: 我发现有一个名为 DrawArc 的例程.NET GDI+ 中的重载几乎可以满足我的需要(稍后将详细介绍“几乎”)。

为了简化查看,以下例程为例:

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

该例程将终点直接放置在X=95,Y=55处。对于圆形椭圆提到的其他例程将导致 X=85, Y=29。如果有一种方法1)不必绘制任何东西并且2)e.Graphics.DrawArc返回终点坐标,这就是我需要的。

现在问题变得更加清晰了 - 有人知道 e.Graphics.DrawArc 是如何实现的吗?

Does anyone have a good algorithm for calculating the end point of ArcSegment? This is not a circular arc - it's an elliptical one.

For example, I have these initial values:

  • Start Point X = 0.251
  • Start Point Y = 0.928
  • Width Radius = 0.436
  • Height Radius = 0.593
  • Start Angle = 169.51
  • Sweep Angle = 123.78

I know the location that my arc should end up at is right around X=0.92 and Y=0.33 (through another program), but I need to do this in an ArcSegment with specifying the end point. I just need to know how to calculate the end point so it would look like this:

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

Does anyone know of a good way to calculate this? (I don't suppose it matters that this is WPF or any other language as the math should be the same).

Here is an image. All values are known in it, except for end point (the orange point).
image depicting arc


EDIT:
I've found that there is a routine called DrawArc with an overload in .NET GDI+ that pretty much does what I need (more on the "pretty much" in a sec).

To simplify viewing it, take the following as an example:

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 routine squarely puts the end point at X=95, Y=55. Other routines mentioned for circular ellipses would result in X=85, Y=29. If there was a way to 1) Not have to draw anything and 2) have e.Graphics.DrawArc return the end-point coordinates, this is what I would need.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

胡大本事 2024-11-02 23:11:53

有人知道e.Graphics.DrawArc是如何实现的吗?

Graphics.DrawArc 调用 gdiplus.dll 中的本机函数 GdipDrawArcI。该函数调用同一 dll 中的 arc2polybezier 函数。它似乎使用贝塞尔曲线来近似椭圆弧。为了获得您正在寻找的完全相同的端点,我们必须对该函数进行逆向工程并弄清楚它到底是如何工作的。

幸运的是, Wine 的好人有 已经为我们做到了

这是 arc2polybezier 方法,大致从 C 翻译为 C# (请注意,因为这是从 Wine 翻译的,所以该代码已获得 LGPL< /a>)

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;
    }
}

使用此代码作为指导,结合一些数学知识,我编写了这个端点计算器类(不是 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;
    }
}

以下是一些示例。请注意,您给出的第一个示例是不正确的 - 对于这些初始值,DrawArc() 的端点为 (0.58, 0.97),不是 (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

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

Graphics.DrawArc calls the native function GdipDrawArcI in gdiplus.dll. This function calls the arc2polybezier function in the same dll. It appears to use a bezier curve to approximate an elliptical arc. In order to get the exact same end-point you're looking for, we'd have to reverse-engineer that function and figure out exactly how it works.

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

Here is the arc2polybezier method, roughly translated from C to C# (note that because this 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, along with a bit of math, I wrote this endpoint calculator class (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. Note that the first example you gave is incorrect - for those initial values, DrawArc() will have an endpoint of (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
亣腦蒛氧 2024-11-02 23:11:53
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)

所以,你的坐标是(xEnd,yEnd)。

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).

美煞众生 2024-11-02 23:11:53

“BlueRaja - Danny Pflughoeft”的答案是正确的,但是......它围绕半径点,必须使用 PointF 代替 Point

PointF radius = new PointF((float)width / 2, (float)height / 2);

我已经扩展了位类以便也有起点,以及每个方法的另一个签名:

  public static class ChartHelper
{
    public static PointF GetStartingPoint(float x, float y, double width, double height, double startAngle, double sweepAngle)
    {
        return GetStartingPoint(new PointF(x, y), width, height, startAngle, sweepAngle);
    }

    public static PointF GetStartingPoint(PointF startPoint, double width, double height, double startAngle, double sweepAngle)
    {
        PointF radius = new PointF((float)width / 2, (float)height / 2);

        //Adjust the angles for the radius width/height
        startAngle = UnstretchAngle(startAngle, radius);

        //Calculate the starting point
        return new PointF
        {
            X = (float)(Math.Cos(startAngle) + 1) * radius.X + startPoint.X,
            Y = (float)(Math.Sin(startAngle) + 1) * radius.Y + startPoint.Y,
        };
    }

    public static PointF GetFinalPoint(float x, float y, double width, double height, double startAngle, double sweepAngle)
    {
        return GetFinalPoint(new PointF(x, y), width, height, startAngle, sweepAngle);
    }

    public static PointF GetFinalPoint(PointF startPoint, double width, double height, double startAngle, double sweepAngle)
    {
        PointF radius = new PointF((float)width / 2, (float)height / 2);
        double endAngle = startAngle + sweepAngle;
        double 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
        double angleMultiplier = (double)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 PointF
        {
            X = (float)(Math.Cos(calculatedEndAngle) + 1) * radius.X + startPoint.X,
            Y = (float)(Math.Sin(calculatedEndAngle) + 1) * radius.Y + startPoint.Y,
        };
    }

    private static double UnstretchAngle(double angle, PointF 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));
        double rotationOffset = (double)Math.Round(radians / (2.0 * Math.PI), MidpointRounding.AwayFromZero) -
                             (double)Math.Round(stretchedAngle / (2.0 * Math.PI), MidpointRounding.AwayFromZero);
        return stretchedAngle + rotationOffset * Math.PI * 2.0;
    }
}

the answer of "BlueRaja - Danny Pflughoeft" is correct but ... it rounds the radius point, a PointF has to be used instead a Point:

PointF radius = new PointF((float)width / 2, (float)height / 2);

I've extended a bit the class in order to have starting points as well, and another signature per method:

  public static class ChartHelper
{
    public static PointF GetStartingPoint(float x, float y, double width, double height, double startAngle, double sweepAngle)
    {
        return GetStartingPoint(new PointF(x, y), width, height, startAngle, sweepAngle);
    }

    public static PointF GetStartingPoint(PointF startPoint, double width, double height, double startAngle, double sweepAngle)
    {
        PointF radius = new PointF((float)width / 2, (float)height / 2);

        //Adjust the angles for the radius width/height
        startAngle = UnstretchAngle(startAngle, radius);

        //Calculate the starting point
        return new PointF
        {
            X = (float)(Math.Cos(startAngle) + 1) * radius.X + startPoint.X,
            Y = (float)(Math.Sin(startAngle) + 1) * radius.Y + startPoint.Y,
        };
    }

    public static PointF GetFinalPoint(float x, float y, double width, double height, double startAngle, double sweepAngle)
    {
        return GetFinalPoint(new PointF(x, y), width, height, startAngle, sweepAngle);
    }

    public static PointF GetFinalPoint(PointF startPoint, double width, double height, double startAngle, double sweepAngle)
    {
        PointF radius = new PointF((float)width / 2, (float)height / 2);
        double endAngle = startAngle + sweepAngle;
        double 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
        double angleMultiplier = (double)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 PointF
        {
            X = (float)(Math.Cos(calculatedEndAngle) + 1) * radius.X + startPoint.X,
            Y = (float)(Math.Sin(calculatedEndAngle) + 1) * radius.Y + startPoint.Y,
        };
    }

    private static double UnstretchAngle(double angle, PointF 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));
        double rotationOffset = (double)Math.Round(radians / (2.0 * Math.PI), MidpointRounding.AwayFromZero) -
                             (double)Math.Round(stretchedAngle / (2.0 * Math.PI), MidpointRounding.AwayFromZero);
        return stretchedAngle + rotationOffset * Math.PI * 2.0;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文