Search code examples
c++openglellipsedrawellipse

Why are my openGL ellipses pointed?


I copied this ellipse code directly from the opengl textbook:

void ellipseMidpoint (int xCenter, int yCenter, int Rx, int Ry)
{
  int Rx2 = Rx * Rx;
  int Ry2 = Ry * Ry;
  int twoRx2 = 2 * Rx2;
  int twoRy2 = 2 * Ry2;
  int p;
  int x = 0;
  int y = Ry;
  int px = 0;
  int py = twoRx2 * y;

//initial points in both quadrants
ellipsePlotPoints (xCenter, yCenter, x, y);

//Region 1
p = round (Ry2 - (Rx2 * Ry) + (0.25 * Rx2));
while (px < py) {
    x++;
    px += twoRy2;
    if (p < 0)
        p += Ry2 + px;
    else {
        y--;
        py -= twoRx2;
        p += Ry2 + px - py;
    }
    ellipsePlotPoints (xCenter, yCenter, x, y);
}

//Region 2
p = round (Ry2 * (x+0.5) * (x+0.5) + Rx2 * (y-1) * (y-1) - Rx2 * Ry2);
while (y > 0) {
    y--;
    py -= twoRx2;
    if (p > 0)
        p += Rx2 - py;
    else {
        x++;
        px += twoRy2;
        p += Rx2 - py + px;
    }
    ellipsePlotPoints (xCenter, yCenter, x, y);
 }
}

void ellipsePlotPoints (int xCenter, int yCenter, int x, int y)
{
    setPixel (xCenter + x, yCenter + y);
    setPixel (xCenter - x, yCenter + y);
    setPixel (xCenter + x, yCenter - y);
    setPixel (xCenter - x, yCenter - y);
}

void setPixel (GLint xPos, GLint yPos)
{
    glBegin (GL_POINTS);
    glVertex2i(xPos, yPos);
    glEnd();
}

The smaller ellipses seem to be fine but the larger ones are pointy and sort of flat at the ends.

Any ideas why?

Here is a current screenshot:

Here's an image


Solution

  • I think you're encountering overflow. I played with your code. While I never saw exactly the same "lemon" type shapes from your pictures, things definitely fell apart at large sizes, and it was caused by overflowing the range of the int variables used in the code.

    For example, look at one of the first assignments:

    int py = twoRx2 * y;
    

    If you substitute, this becomes:

    int py = 2 * Rx * Rx * Ry;
    

    If you use a value of 1000 each for Rx and Ry, this is 2,000,000,000. Which is very close to the 2^31 - 1 top of the range of a 32-bit int.

    If you want to use this algorithm for larger sizes, you could use 64-bit integer variables. Depending on your system, the type would be long or long long. Or more robustly, int64_t after including <stdint.h>.

    Now, if all you want to do is draw an ellipsis with OpenGL, there are much better ways. The Bresenham type algorithms used in your code are ideal if you need to draw a curve pixel by pixel. But OpenGL is a higher level API, which knows how to render more complex primitives than just pixels. For a curve, you will most typically use a connected set of line segments to approximate the curve. OpenGL will then take care of turning those line segments into pixels.

    The simplest way to draw an ellipsis is to directly apply the parametric representation. With phi an angle between 0 and PI, and using the naming from your code, the points on the ellipsis are:

    x = xCenter + Rx * cos(phi)
    y = yCenter + Ry * sin(phi)
    

    You can use an increment for phi that meets your precision requirements, and the code will look something to generate an ellipsis approximated by DIV_COUNT points will look something like this:

    float angInc = 2.0f * m_PI / (float)DIV_COUNT;
    float ang = 0.0f;
    glBegin(GL_LINE_LOOP);
    for (int iDiv = 0; iDiv < DIV_COUNT; ++iDiv) {
        ang += angInc;
        float x = xCenter + Rx * cos(ang);
        float y = yCenter + Ry * sin(ang);
        glVertex2f(x, y);
    glEnd();
    

    If you care about efficiency, you can avoid calculating the trigonometric functions for each point, and apply an incremental rotation to calculate each point from the previous one:

    float angInc = 2.0f * M_PI / (float)DIV_COUNT;
    float cosInc = cos(angInc);
    float sinInc = sin(angInc);
    float cosAng = 1.0f;
    float sinAng = 0.0f
    glBegin(GL_LINE_LOOP);
    for (int iDiv = 0; iDiv < DIV_COUNT; ++iDiv) {
        float newCosAng = cosInc * cosAng - sinInc * sinAng;
        sinAng = sinInc * cosAng + cosInc * sinAng;
        cosAng = newCosAng;
        float x = xCenter + Rx * cosAng;
        float y = yCenter + Ry * sinAng;
        glVertex2f(x, y);
    glEnd();
    

    This code is of course just for illustrating the math, and to get you started. In reality, you should use current OpenGL rendering methods, which includes vertex buffers, etc.