存在 start/end 个角度问题的 OpenGL 椭圆

OpenGL Ellipse with start/end angle issues

我在使用 OpenGL(准确地说是 sharpgl,因为它与 WPF 配合得很好)绘制椭圆对象时遇到问题。

现在我正在使用这段代码(角度以度为单位):

gl.Begin(OpenGL.GL_LINE_STRIP);
gl.Color(colorR, colorG, colorB, alfa);
for (double i = angleStart; i <= angleEnd; ++i)
{
    double angleCurrent = Math.PI * i / 180;

    double dx = radiusX * Math.Cos(angleCurrent);
    double dy = radiusY * Math.Sin(angleCurrent);

    gl.Vertex(dx + ellipseMiddleCoordX, dy + ellipseMiddleCoordY, 0);
}

gl.End();

绘制圆(即 radiusX = radiusY)和角度为 0、90、180、270、360(radiusX != radiusY)的椭圆时效果 100%。

但是当 radiusX != radiusY 例如 angleStart = 30; angleEnd = 70.

时,它并不能很好地完成工作

它在上面的示例中做了什么,它首先绘制一个圆,应用角度,然后重新计算 X/Y 半径。这给出了不正确的结果,因为我期望是这样的:

其中红色 = 预期行为,黄色 = 上面的代码,灰色 = 额外的角度线

我完全不知道我应该如何解决这个问题。

P.S. 是的 - 我知道 gl.Begin/gl.End 是过时的函数,但我仍然无法掌握 OGL 的完整概念,而且我的大部分程序只是数学。

那是因为椭圆参量角不是几何角,因为它被每个轴上的不同比例(半径)扭曲了。请参阅这些相关的 QA 以获取一些灵感:

  • I need an equation for equal movement along an ellipse
  • Algorithm for shape calculation (Ellipse)

解决这个问题的方法是近似失真(但如果偏心率变化很大就会有问题)或搜索正确的角度(简单但缓慢)或分析计算(涉及一些数学但速度很快)。

对于搜索,您可以使用 二进制搜索 为此,请记住几何角度在某些象限上将大于参数角度,而在其他象限上则小于。

这里是小的 C++/OpenGL 示例(简单且未优化以使其尽可能简单):

//---------------------------------------------------------------------------
const float pi2=2.0*M_PI;
const float deg=M_PI/180.0;
//---------------------------------------------------------------------------
float ellipse_angle(float rx,float ry,float ang)    // geometrical angle [rad] -> ellipse parametric angle [rad]
    {
    float da,ea,ga;
    ang=fmod(ang,pi2);                              // normalize to <0,pi2>
    ea=ang;                                         // start elliptic angle
    ga=atan2(ry*sin(ea),rx*cos(ea));                // compute geometrical angle from it
    if (ga<0.0) ga+=pi2;                            // normalize to <0,pi2>
    if (ga<=ang)                                    // ea >= ang
        {
        da=90.0*deg-fmod(ang,90.0*deg);             // range to search
        da*=0.5;
        for (ea=ang;da>1e-10;da*=0.5)               // binary search
            {
            ea+=da;                                 // test next elliptic angle
            ga=atan2(ry*sin(ea),rx*cos(ea));        // compute geometrical angle from it
            if (ga<0.0) ga+=pi2;                    // normalize to <0,pi2>
            if (ga>ang) ea-=da;                     // restore original angle if too big
            }
        }
    else{                                           // ea <= ang
        da=fmod(ang,90.0*deg);                      // range to search
        da*=0.5;
        for (ea=ang;da>1e-10;da*=0.5)               // binary search
            {
            ea-=da;                                 // test next elliptic angle
            ga=atan2(ry*sin(ea),rx*cos(ea));        // compute geometrical angle from it
            if (ga<0.0) ga+=pi2;                    // normalize to <0,pi2>
            if (ga<ang) ea+=da;                     // restore original angle if too small
            }
        }

    return ea;
    }
//---------------------------------------------------------------------------
void gl_draw()
    {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glScalef(1.0,float(xs)/float(ys),1.0);
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glDisable(GL_DEPTH_TEST);

    int i,n=100;
    float rx=0.75,ry=0.5,a0=25.0*deg,a1=115.0*deg;
    float x,y,a,da,ea0,ea1,dd;

    // test whole circumference
    for (dd=10.0*deg,a0=0.0,a1=a0+0.75*dd;a1<pi2;a0+=dd,a1+=dd)
        {
        // get parametric angles
        ea0=ellipse_angle(rx,ry,a0);
        ea1=ellipse_angle(rx,ry,a1);
        // render elliptic arc
        glBegin(GL_LINE_STRIP);
        glColor3f(0.3,0.3,1.0);
        for (a=ea0,da=(ea1-ea0)/float(n-1),i=0;i<n;i++,a+=da)
            {
            x=rx*cos(a);
            y=ry*sin(a);
            glVertex2f(x,y);
            }
        glEnd();
        // render geometric axises (for visual check)
        glBegin(GL_LINE_STRIP);
        glColor3f(0.3,0.3,0.0);
        glVertex2f(2.0*cos(a0),2.0*sin(a0));
        glVertex2f(0.0,0.0);
        glVertex2f(2.0*cos(a1),2.0*sin(a1));
        glEnd();
        }

    glFlush();
    SwapBuffers(hdc);
    }
//---------------------------------------------------------------------------

其中 xs,ys 是我的 OpenGL 的分辨率 window 并且 M_PI=3.1415926535897932384626433832795 注意所有角度都以弧度为单位。

此处预览:

如您所见,角度在整个圆周上都匹配(蓝色圆弧与黄线对齐)...

函数float ellipse_angle(float rx,float ry,float ang)将return椭圆参数角对应于轴上的几何角ang与半轴对齐的椭圆rx,ry

有很多东西可以提高速度,比如使用 rx/ry 和只缩放一个轴,预先计算角度常数等。

[Edit1] 稍微好一点的代码

使用宽高比和渲染也进行了更改,以便更好地进行视觉检查

//---------------------------------------------------------------------------
const float pi2=2.0*M_PI;
const float deg=M_PI/180.0;
//---------------------------------------------------------------------------
float ellipse_angle(float rx,float ry,float ang)    // geometrical angle [rad] -> ellipse parametric angle [rad]
    {
    float da,ea,ga,aspect;
    aspect=rx/ry;                                   // aspect ratio for speed up
    ang=fmod(ang,pi2);                              // normalize to <0,pi2>
    ea=ang;                                         // start elliptic angle
    ga=atan2(sin(ea),aspect*cos(ea));               // compute geometrical angle from it
    if (ga<0.0) ga+=pi2;                            // normalize to <0,pi2>
    if (ga<=ang)                                    // ea >= ang
        {
        da=90.0*deg-fmod(ang,90.0*deg);             // range to search
        da*=0.5;
        for (ea=ang;da>1e-10;da*=0.5)               // binary search
            {
            ea+=da;                                 // test next elliptic angle
            ga=atan2(sin(ea),aspect*cos(ea));       // compute geometrical angle from it
            if (ga<0.0) ga+=pi2;                    // normalize to <0,pi2>
            if (ga>ang) ea-=da;                     // restore original angle if too big
            }
        }
    else{                                           // ea <= ang
        da=fmod(ang,90.0*deg);                      // range to search
        da*=0.5;
        for (ea=ang;da>1e-10;da*=0.5)               // binary search
            {
            ea-=da;                                 // test next elliptic angle
            ga=atan2(sin(ea),aspect*cos(ea));       // compute geometrical angle from it
            if (ga<0.0) ga+=pi2;                    // normalize to <0,pi2>
            if (ga<ang) ea+=da;                     // restore original angle if too small
            }
        }
    return ea;
    }
//---------------------------------------------------------------------------
void gl_draw()
    {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glScalef(1.0,float(xs)/float(ys),1.0);
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glDisable(GL_DEPTH_TEST);

    int i,n=100;
    float rx=0.95,ry=0.35;
    float x,y,a,dd,da,a0,a1,ea0,ea1;

    // test whole circumference
    for (dd=10.0*deg,a0=0.0,a1=a0+0.75*dd;a1<pi2;a0+=dd,a1+=dd)
        {
        // render geometric angle pies (for visual check)
        glBegin(GL_TRIANGLES);
        glColor3f(0.1,0.1,0.1);
        glVertex2f(cos(a0),sin(a0));
        glVertex2f(0.0,0.0);
        glVertex2f(cos(a1),sin(a1));
        glEnd();

        // get parametric angles (rx,ry)
        ea0=ellipse_angle(rx,ry,a0);
        ea1=ellipse_angle(rx,ry,a1);
        // render elliptic arc (rx,ry)
        glBegin(GL_LINE_STRIP);
        glColor3f(0.3,0.7,1.0);
        for (a=ea0,da=(ea1-ea0)/float(n-1),i=0;i<n;i++,a+=da)
            {
            x=rx*cos(a);
            y=ry*sin(a);
            glVertex2f(x,y);
            }
        glEnd();

        // get parametric angles (ry,rx)
        ea0=ellipse_angle(ry,rx,a0);
        ea1=ellipse_angle(ry,rx,a1);
        // render elliptic arc (ry,rx)
        glBegin(GL_LINE_STRIP);
        glColor3f(1.0,0.7,0.3);
        for (a=ea0,da=(ea1-ea0)/float(n-1),i=0;i<n;i++,a+=da)
            {
            x=ry*cos(a);
            y=rx*sin(a);
            glVertex2f(x,y);
            }
        glEnd();
        }

    glFlush();
    SwapBuffers(hdc);
    }
//---------------------------------------------------------------------------

仍有改进的余地,例如完全摆脱 atan2 并使用叉积代替,就像我在这里所做的那样:

[Edit2]快速解析O(1)解决方案

我简单地使用了参数线方程:

x(t) = t*cos(ang)
y(t) = t*sin(ang)

和隐式椭圆方程:

(x/rx)^2 + (y/ry)^2 = 1

计算交点(求解 t 并从中计算 x,y),然后对其应用逆畸变以从椭圆映射到圆形(通过缩放 y*=rx/ry)和最后用 atan2(y,x).

计算它的角度

此处C++代码:

//---------------------------------------------------------------------------
const float pi2=2.0*M_PI;
const float deg=M_PI/180.0;
//---------------------------------------------------------------------------
float ellipse_angle(float rx,float ry,float ang)    // geometrical angle [rad] -> ellipse parametric angle [rad]
    {
    float x,y,t,aa,bb,ea;
    x=cos(ang);                                     // axis direction at angle ang
    y=sin(ang);
    aa=rx*rx; bb=ry*ry;                             // intersection between ellipse and axis
    t=aa*bb/((x*x*bb)+(y*y*aa));
    x*=t; y*=t;
    y*=rx/ry;                                       // convert to circle
    ea=atan2(y,x);                                  // compute elliptic angle
    if (ea<0.0) ea+=pi2;                            // normalize to <0,pi2>
    return ea;
    }
//---------------------------------------------------------------------------