الحصول على نقطة النهاية في ArcSegment باستخدام Start X/Y وStart+Sweep Angles

StackOverflow https://stackoverflow.com/questions/5441061

سؤال

هل لدى أي شخص خوارزمية جيدة لحساب نقطة النهاية 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 أو أي لغة أخرى لأن الرياضيات يجب أن تكون هي نفسها).

هنا صورة.جميع القيم معروفة فيه، باستثناء نقطة النهاية (النقطة البرتقالية).image depicting arc


يحرر:لقد وجدت أن هناك روتين يسمى 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 تم تنفيذه؟

هل كانت مفيدة؟

المحلول

هل يعرف أحد كيف يتم تنفيذ e.Graphics.DrawArc؟

Graphics.DrawArc يستدعي الوظيفة الأصلية GdipDrawArcI في gdiplus.dll.هذه الوظيفة تستدعي arc2polybezier وظيفة في نفس دلل.يبدو أنه يستخدم منحنى بيزير لتقريب القوس الإهليلجي.من أجل الحصول على بالضبط نفس نقطة النهاية التي تبحث عنها، سيتعين علينا إجراء هندسة عكسية لتلك الوظيفة ومعرفة كيفية عملها بالضبط.

لحسن الحظ، أهل الخير في خمر يملك فعلت ذلك بالفعل بالنسبة لنا.

إليك طريقة arc2polybezier، مترجمة تقريبًا من C إلى C# (لاحظ أنه نظرًا لترجمة هذا من Wine، فقد تم ترخيص هذا الرمز بموجب 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;
    }
}

باستخدام هذا الرمز كدليل، إلى جانب القليل من الرياضيات، كتبت هذا الفصل الخاص بآلة حاسبة نقطة النهاية (وليس 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

نصائح أخرى

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

هل هذا من المساعدة:
الرياضيات من ArcSegment

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top