시작/끝 각도 문제가 있는 OpenGL 타원 (OpenGL Ellipse with start/end angle issues)


문제 설명

시작/끝 각도 문제가 있는 OpenGL 타원 (OpenGL Ellipse with start/end angle issues)

OpenGL을 사용하여 타원 개체를 그리는 데 문제가 있습니다(WPF에서 충분히 잘 작동하므로 정확히는 sharpgl).

지금 이 코드를 사용하고 있습니다(각도 단위 ):

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();
</code></pre>

그리고 원(즉, radiusX = radiusY)과 각도가 0, 90, 180, 270, 360인 타원(radiusX != radiusY).

그러나 radiusX != radiusY 및 예를 들어 angleStart = 30; angleEnd = 70.

위의 예에서 수행하는 작업은 먼저 원을 그리고 각도를 적용한 다음 X/Y 반경을 다시 계산합니다. 다음과 같은 결과를 예상했기 때문에 잘못된 결과가 나타납니다.

<img src="https:


참조 솔루션

방법 1:

That is because the elliptic parametric angle is not geometric angle as it is distorted by the different scale (radius) in each axis. See these related QAs for some inspiration:

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

  • </ul>

    The way to solve this is ether approximate the distortion (but that will beproblematic if eccentricity change a lot) or search for correct angle (simple but slow) or compute it analytically (involves some math but fast).

    For the search you can use binary search for this just take in mind the geometric angle will be bigger than parametric one on some quadrants and less on the others.

    Here small C++/OpenGL example (unsophisticated and unoptimized to keep it as simple as possible):

    //‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑
    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);
        }
    //‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑
    

    where xs,ys is the resolution of my OpenGL window and M_PI=3.1415926535897932384626433832795 note all angles are in radians.

    Here preview:

    preview

    As you can see the angles match on the whole circumference (blue arcs align with yellow lines) ...

    The function float ellipse_angle(float rx,float ry,float ang) will return elliptic parametric angle that corresponds to geometric angle ang on axis aligned ellipse with half axises rx,ry.

    There are quite a few things that can improve speed of this like using rx/ry and scaling just one axis, precompute the angle constants etc.

    [Edit1] slightly better code

    using aspect ratio and render is changed too for better visual check

    //‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑
    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);
        }
    //‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑‑
    

    preview2

    There still room for improvement like get rid of atan2 completely and use cross product instead like I did in here:

    [Edit2] Fast analytic O(1) solution

    I simply used parametric line equation:

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

    and implicit ellipse equation:

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

    to compute the intersection point (solve t and compute x,y from it), then apply inverse distortion on it to map from ellipse to circle (by scaling y*=rx/ry) and finally compute its angle with atan2(y,x).

    ellipse

    Here C++ code:

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

    (by BoxBishopSpektre)

    참조 문서

    1. OpenGL Ellipse with start/end angle issues (CC BY‑SA 2.5/3.0/4.0)

#opengl-compat #geometry #C# #ellipse #math






관련 질문

레거시 OpenGL에서 독립적인 투명도 주문 (Order independent transparency in legacy OpenGL)

나는 검은 질감을 가지고있다. (I have a black texture)

시작/끝 각도 문제가 있는 OpenGL 타원 (OpenGL Ellipse with start/end angle issues)

행렬 기반의 OpenGL로 표시 (Displaying in openGL based on a matrix)

Python에서 OpenGL로 텍스처를 렌더링하는 것은 (Rendering a texture with OpenGL in Python is not)

OpenTK C#에서 객체 방향 가져오기 (Get orientation of object in OpenTK C#)







코멘트