在给定角度的椭圆上找到点,然后逆转该过程

时间:2019-02-26 22:31:00

标签: c#

我的第一个任务很简单:找到要在屏幕上绘制的椭圆点。我用下面的方法制作了一个Ellipse类,该方法接受0到2 * PI之间的角度并返回该点。

public class Ellipse
{
    public PointF Center { get; set; }
    public float A { get; set; }  /* horizontal semiaxis */
    public float B { get; set; }  /* vertical semiaxis */

    public Ellipse(PointF center, float a, float b)
    {
        this.Center = center;
        this.A = a;
        this.B = b;
    }        

    public PointF GetXYWhenT(float t_rad)
    {
        float x = this.Center.X + (this.A * (float)Math.Cos(t_rad));
        float y = this.Center.Y + (this.B * (float)Math.Sin(t_rad));
        return new PointF(x, y);
    }
}

我使用椭圆的参数方程式来完成此任务很方便。参数t是角度。计算X和Y值并将它们放在一起作为椭圆上的一个点。通过增加参数t,我可以按使绘制椭圆像连接点一样简单的顺序获得点。

private void RunTest1()
{
    PointF center = new PointF(0, 0);
    float a = 3;  /* horizontal semiaxis */
    float b = 4;  /* vertical semiaxis */
    Ellipse ellipse = new Ellipse(center, a, b);

    List<PointF> curve = new List<PointF>();   /* collects all points needed to draw the ellipse */

    float start = 0;
    float end = (float)(2 * Math.PI);  /* 360 degrees */
    float step = 0.0174533f;  /* 1 degree */

    for (float t_rad = start; t_rad <= end; t_rad += step)
    {
        PointF point = ellipse.GetXYWhenT(t_rad);
        curve.Add(point);
    }
}

RunTest X 是我在Main中运行的方法。第一个会给我画椭圆的点。要点是正确的。 我可以通过绘制方法直观地确认椭圆被绘制到规范中,这里将不再介绍。 绘制不是问题。这里的要点是,对于t_rad的每个值,我在曲线上都有一个对应的点。

现在,在绘制椭圆后,我需要执行其他任务。为此,我需要反向处理,因为我在椭圆上取了一个任意点并将其转换回t_rad。 Math.Atan2应该可以解决问题。该方法称为GetTWhenPoint。这是MyMath类中的扩展方法。

public static class MyMath
{
    public static float GetTWhenPoint(this PointF center, PointF point)
    {
        float x = point.X - center.X;
        float y = point.Y - center.Y;

        float retval = (float)Math.Atan2(y, x);
        if (retval < 0)
        {
            retval += (float)(2 * Math.PI);
        }

        return retval;
    }
}

简单的三角函数,对吗?但是...

private void RunTest2()
{
    PointF center = new PointF(0, 0);
    float a = 3;  /* horizontal semiaxis */
    float b = 4;  /* vertical semiaxis */
    Ellipse ellipse = new Ellipse(center, a, b);

    string debug = "TEST 2\r\n";

    float start = 0;
    float end = (float)(2 * Math.PI);  
    float step = 0.0174533f;      

    for (float t_rad = start; t_rad <= end; t_rad += step)
    {
        PointF point = ellipse.GetXYWhenT(t_rad);
        double t_rad2 = center.GetTWhenPoint(point);
        debug += t_rad.ToString() + "\t" + t_rad2.ToString() + "\r\n";
    }

    Clipboard.SetText(debug);
}

当我使用它将点转换回t_rad2时,我希望它等于或近似于原始t_rad。

TEST 2
0   0
0.0174533   0.0232692267745733
0.0349066   0.0465274415910244
0.0523599   0.0697636753320694
0.0698132   0.0929670184850693
0.0872665   0.116126760840416
...
6.178444    6.14392471313477
6.195897    6.1670298576355
6.21335 6.19018936157227
6.230803    6.21339273452759
6.248257    6.23662853240967
6.26571 6.25988674163818
6.283163    6.28315591812134

我在这里想念什么?到目前为止,我的所有数字都以弧度表示(据我所知)。现在这很奇怪...

private void RunTest3()
{
    PointF center = new PointF(0, 0);
    float a = 4;  /* horizontal semiaxis */
    float b = 4;  /* vertical semiaxis */
    Ellipse ellipse = new Ellipse(center, a, b);

    string debug = "TEST 3\r\n";

    float start = 0;
    float end = (float)(2 * Math.PI);
    float step = 0.0174533f;  

    for (float t_rad = start; t_rad <= end; t_rad += step)
    {
        PointF point = ellipse.GetXYWhenT(t_rad);
        double t_rad2 = center.GetTWhenPoint(point);
        debug += t_rad.ToString() + "\t" + t_rad2.ToString() + "\r\n";
    }

    Clipboard.SetText(debug);    
}

如果我将a和b设置为使椭圆成为一个完美的圆,那么一切看起来都很正常!

TEST 3
0   0
0.0174533   0.0174532998353243
0.0349066   0.0349065996706486
0.0523599   0.0523599050939083
0.0698132   0.0698131918907166
0.0872665   0.0872664898633957
...    
6.178444    6.17844390869141
6.195897    6.19589710235596
6.21335 6.21335029602051
6.230803    6.23080348968506
6.248257    6.24825668334961
6.26571 6.26570987701416
6.283163    6.28316307067871

这告诉我的是,当我将点转换回t_rad2时,它会受到椭圆尺寸的影响。但是如何?除了椭圆相对于笛卡尔原点(0,0)的中心调整之外,GetTWhenPoint方法没有利用Ellipse类中的任何其他信息,特别是半轴。 Math.Atan2只需要该点的x和y值即可找到其与0度矢量所成的角度。这是基本的三角学。

它甚至不必关心椭圆上的一点。从方法的上下文来看,这就像无限地指向其他任何点一样。椭圆的尺寸如何影响我的扩展方式?

我的数学错了吗?我的意思是,自从使用Trig以来已经有一段时间了,但是我想我正确地记住了简单的方法。

谢谢!

1 个答案:

答案 0 :(得分:2)

我想这就是你想要的。

public class Ellipse
{
    public PointF Center { get; set; }
    public float A { get; set; }  /* horizontal semiaxis */
    public float B { get; set; }  /* vertical semiaxis */

    public Ellipse(PointF center, float a, float b)
    {
        this.Center=center;
        this.A=a;
        this.B=b;
    }

    public PointF GetXYWhenT(float t_rad)
    {
        float x = this.Center.X+(this.A*(float)Math.Cos(t_rad));
        float y = this.Center.Y+(this.B*(float)Math.Sin(t_rad));
        return new PointF(x, y);
    }

    public float GetParameterFromPoint(PointF point)
    {
        var x = point.X-Center.X;
        var y = point.Y-Center.Y;

        // Since x=a*cos(t) and y=b*sin(t), then
        // tan(t) = sin(t)/cos(t) = (y/b) / (x/a)
        return (float)Math.Atan2(A*y, B*x);
    }
}

class Program
{
    static readonly Random rng = new Random();
    static void Main(string[] args)
    {
        var center = new PointF(35.5f, -12.2f);
        var ellipse = new Ellipse(center, 18f, 44f);

        // Get t between -π and +π
        var t = (float)(2*Math.PI*rng.NextDouble()-Math.PI);
        var point = ellipse.GetXYWhenT(t);

        var t_check = ellipse.GetParameterFromPoint(point);

        Debug.WriteLine($"t={t}, t_check={t_check}");
        // t=-0.7434262, t_check=-0.7434263
    }
}

我认为曲线的正确参数化是一条参数,其参数范围在0到1之间。因此不再需要指定弧度

x = A*Cos(2*Math.PI*t)
y = B*Sin(2*Math.PI*t)

和相反

t = Atan2(A*y, B*x)/(2*PI)

还要考虑相对于中心的极坐标中的椭圆形。

x = A*Cos(t) = R*Cos(θ)     |  TAN(θ) = B/A*TAN(t)
y = B*Sin(t) = R*Sin(θ)     |  
                            |  R = Sqrt(B^2+(A^2-B^2)*Cos(t)^2)

                    A*B 
  R(θ) = ----------------------------
          Sqrt(A^2+(B^2-A^2)*Cos(θ)^2)

另外,请考虑以下辅助函数,这些辅助函数将所需的象限(弧度形式)包裹角度

/// <summary>
/// Wraps angle between 0 and 2π
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
public static double WrapTo2PI(this double angle) 
    => angle-(2*Math.PI)*Math.Floor(angle/(2*Math.PI));
/// <summary>
/// Wraps angle between -π and π
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
public static double WrapBetweenPI(this double angle) 
    => angle+(2*Math.PI)*Math.Floor((Math.PI-angle)/(2*Math.PI));

和学位版本

/// <summary>
/// Wraps angle between 0 and 360
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
public static double WrapTo360(this double angle) 
    => angle-360*Math.Floor(angle/360);
/// <summary>
/// Wraps angle between -180 and 180
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
/// <remarks>see: http://stackoverflow.com/questions/7271527/inconsistency-with-math-round</remarks>
public static double WrapBetween180(this double angle)
    => angle+360*Math.Floor((180-angle)/360);