获取终点在ArcSegment与开始X / Y和启动+后掠角 [英] Getting End Point in ArcSegment with Start X/Y and Start+Sweep Angles

查看:616
本文介绍了获取终点在ArcSegment与开始X / Y和启动+后掠角的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

例如,我有这些初始值:

  • 在起点X = 0.251
  • 在起点Y = 0.928
  • 在宽度半径= 0.436
  • 高度半径= 0.593
  • 在起始角= 169.51
  • 后掠角= 123.78

我知道我的圆弧应该结束了在位置是右绕X = 0.92和Y = 0.33(通过另一个程序),但我必须这样做在 ArcSegment 与指定终点。我只需要知道如何计算终点所以它是这样的:

 < ArcSegment大小=0.436,0.593点=0.92,0.33IsLargeArc =FALSESweepDirection =顺时针/>
 

有谁知道来计算这是一个好办法? (我不认为它很重要,这是WPF或任何其他语言的数学应该是一样的)。

下面是一个图像。所有的值是已知的话,除了终点(橙色点)。


编辑: 我发现有一个名为常规 DrawArc 在.NET中使用GDI过载+ 是pretty的多少做什么,我需要(在一个秒多pretty的东西)。

要简化查看,采取以下为例:

 公用Sub MyDrawArc(五作为PaintEventArgs的)

    昏暗blackPen作为新笔(Color.Black,2)
    昏暗x As中单= 0.0F
    昏暗y为单= 0.0F
    昏暗的宽度为单= 100.0F
    昏暗的高度单= 200.0F

    昏暗startAngle开始单= 180.0F
    昏暗sweepAngle单= 135.0F

    e.Graphics.DrawArc(blackPen,X,Y,宽度,高度,startAngle开始,sweepAngle)

    昏暗redPen作为新笔(Color.Red,2)
    e.Graphics.DrawLine(redPen,新点(0,55),新点(95,55))
结束小组

私人小组ImageBox_Paint(发送者为对象,E作为System.Windows.Forms.PaintEventArgs)处理ImageBox.Paint
    MyDrawArc(五)
结束小组
 

此程序正视提出结束点 X = 95,Y = 55 。提到圆形椭圆其它程序会导致 X = 85,Y = 29 。如果有一种方法为 1)是不是一定要画什么和 2) e.Graphics.DrawArc 返回终点的坐标,这就是我所需要的。

所以,现在的问题涨势一些清晰 - 没有人知道如何 e.Graphics.DrawArc 实施

解决方案
  

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

Graphics.DrawArc 调用的gdiplus.dll本机的功能 GdipDrawArcI 。该函数调用同一个DLL的 arc2polybezier 功能。它似乎使用Be​​zier曲线来近似椭圆弧。为了得到的确切的相同的终点,你要寻找的,我们不得不反向工程的功能,并弄清楚它究竟是如何工作的。

幸运的是,善良的人们在葡萄酒拥有的already做到这一点对我们来说

下面是arc2polybezier方法,从C大致翻译到C#的(注意,因为这是从酒翻译,这code下的 LGPL 的:

 内部类GdiPlus
{
    公共const int的MAX_ARC_PTS = 13;

    公共静态INT arc2polybezier(点[]点,双X1,Y1双,双X2双Y2,
                              双startAngle开始,双sweepAngle)
    {
        INT I;
        双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和end_angle是迭代变量* /
        start_angle = startAngle开始;

        对于(i = 0; I< MAX_ARC_PTS  -  1; i + = 3)
        {
            / *检查,如果我们已经打捞结束角* /
            如果(sweepAngle> 0.0)
            {
                如果(start_angle> = endAngle)打破;
                end_angle = Math.Min(start_angle + Math.PI / 2,endAngle);
            }
            其他
            {
                如果(start_angle< = endAngle)打破;
                end_angle = Math.Max​​(start_angle  -  Math.PI / 2,endAngle);
            }

            如果(分!= NULL)
            {
                点[] returnedPoints = add_arc_part(X1,Y1,X2,Y2,start_angle,end_angle岛== 0);
                // add_arc_part返回一个Point []大小4
                为(诠释J = 0; J&4;; J ++)
                    点[I + J] = returnedPoints [J]。
            }
            start_angle + = Math.PI / 2 *(sweepAngle&所述; 0.0 -1.0:1.0);
        }

        如果(我== 0)
            返回0;
        返回I + 1;
    }

    公共静态无效unstretch_angle(REF双角,双rad_x,双rad_y)
    {
        角= deg2rad(角度);

        如果(Math.Abs​​(Math.Cos(角))≤0.00001 || Math.Abs​​(Math.Sin(角))≤; 0.00001)
            返回;

        双拉伸= Math.Atan2(Math.Sin(角度)/Math.Abs​​(rad_y),Math.Cos(角度)/Math.Abs​​(rad_x));
        INT revs_off =(int)的Math.Round(角/(2.0 * Math.PI),MidpointRounding.AwayFromZero) - 
                       (INT)Math.Round(拉伸/(2.0 * Math.PI),MidpointRounding.AwayFromZero);
        拉伸+ = revs_off * Math.PI * 2.0;
        角=捉襟见肘;
    }

    公共静态双deg2rad(双学位)
    {
        返回Math.PI *度/ 180.0;
    }

    私人静点[] add_arc_part(双X1,Y1双,双X2双Y2,
                                     双启动,双端,布尔WRITE_FIRST)
    {
        双center_x,
               center_y,
               rad_x,
               rad_y,
               cos_start,
               cos_end,
               sin_start,
               sin_end,
               一个,
               半;
        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(开始);
        cos_end = Math.Cos(完)
        sin_start = Math.Sin(开始);
        sin_end = Math.Sin(完)

        半=(结束 - 开始)/2.0;
        A = 4.0 / 3.0 *(1  -  Math.Cos(半))/ Math.Sin(半);

        点[] PT =新点[4]。
        如果(WRITE_FIRST)
        {
            PT [0] .X = cos_start;
            PT [0] .Y = sin_start;
        }
        角[1] .X = cos_start  - 一个* sin_start;
        角[1] .Y = sin_start +一* cos_start;

        PT [3] .X = cos_end;
        角[3] .Y = sin_end;
        角[2] .X = cos_end +一* sin_end;
        角[2] .Y = sin_end  - 一个* cos_end;

        / *扩大分从单位圆回椭圆* /
        为(ⅰ=(WRITE_FIRST?0:1);我4;;我+ +)
        {
            PT [I] .X = PT [I] .X * rad_x + center_x;
            PT [I] .Y = PT [I] .Y * rad_y + center_y;
        }
        返回角;
    }
}
 

使用这个code为指导,再加上一点数学的,我写了这个端点计算器类的(不是LGPL)的:

 使用系统;
使用System.Windows;

内部类DrawArcEndPointCalculator
{
    公共点GetFinalPoint(点的startPoint,双倍宽度,双倍高度,
                               双startAngle开始,双sweepAngle)
    {
        点半径=新的点(宽/ 2.0,高度/ 2.0);
        双endAngle =由startAngle + sweepAngle;
        INT sweepDirection =(sweepAngle℃,-1:1);

        //调整角度为半径宽/高
        由startAngle = UnstretchAngle(startAngle开始,半径);
        endAngle = UnstretchAngle(endAngle,半径);

        //确定多少次扫角添加到起始角
        INT angleMultiplier =(int)的Math.Floor(2 * sweepDirection *(endAngle  - 由startAngle)/Math.PI)+ 1;
        angleMultiplier = Math.Min(angleMultiplier,4);

        清扫后//计算最后得到的角
        双calculatedEndAngle =由startAngle + angleMultiplier * Math.PI / 2 * sweepDirection;
        calculatedEndAngle = sweepDirection * Math.Min(sweepDirection * calculatedEndAngle,sweepDirection * endAngle);

        //计算最后一点
        返回新点
        {
            X =(Math.Cos(calculatedEndAngle)+1)* radius.X + startPoint.X,
            Y =(Math.Sin(calculatedEndAngle)+1)* radius.Y + startPoint.Y,
        };
    }

    私人双人UnstretchAngle(双角,角半径)
    {
        双弧度= Math.PI *角/ 180.0;

        如果(Math.Abs​​(Math.Cos(弧度))≤0.00001 || Math.Abs​​(Math.Sin(弧度))≤; 0.00001)
            返回弧度;

        双stretchedAngle = Math.Atan2(Math.Sin(弧度)/ Math.Abs​​(radius.Y),Math.Cos(弧度)/ Math.Abs​​(radius.X));
        INT rotationOffset =(int)的Math.Round(弧度/(2.0 * Math.PI),MidpointRounding.AwayFromZero) - 
                             (INT)Math.Round(stretchedAngle /(2.0 * Math.PI),MidpointRounding.AwayFromZero);
        返回stretchedAngle + rotationOffset * Math.PI * 2.0;
    }
}
 

下面是一些例子。需要注意的是你给的第一个例子是不正确 - 对于那些初始值, DrawArc()将有(0.58,0.97)端点,不可以 (0.92,0.33)。

 点的startPoint =新的点(0,0);
双幅= 100;
双高= 200;
双startAngle开始= 180;
双sweepAngle = 135;
DrawArcEndPointCalculator _endPointCalculator =新DrawArcEndPointCalculator();
点lastPoint = _endPointCalculator.GetFinalPoint(的startPoint,宽度,高度,startAngle开始,sweepAngle);
Console.WriteLine(X = {0},Y = {1},lastPoint.X,lastPoint.Y);
//输出:X = 94.7213595499958,Y = 55.2786404500042

的startPoint =新的点(0.251,0.928);
宽度= 0.436;
身高= 0.593;
由startAngle = 169.51;
sweepAngle = 123.78;
_endPointCalculator.GetFinalPoint(的startPoint,宽度,高度,startAngle开始,sweepAngle);
//返回x = 0.579143189905416,Y = 0.968627455618129

点的startPoint =新点(0,0);
双幅= 20;
双高= 30;
双startAngle开始= 90;
双sweepAngle = 90;
_endPointCalculator.GetFinalPoint(的startPoint,宽度,高度,startAngle开始,sweepAngle);
//返回X = 0,Y = 15
 

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


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?

解决方案

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

这篇关于获取终点在ArcSegment与开始X / Y和启动+后掠角的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆