Draw a curved line from an arc edge

3.2k views Asked by At

Here's the screenshot of what I am doing. Currently, I'm stuck from drawing a curved borders into this rectangle.

naah

My first solution was: draw a quartered circle behind the rectangle, but if I adjust the opacity of the shape, as you can see, the quartered circle gets shown.

neeh

I know this is pretty basic for you guys but I'm not really good at math.

I did try to reuse the computed edges of the arc and add the size of border but I got this as a result.

niih

I also think of bezier curves as a replacement but I think it is more efficient to just reuse the computed vertices and add all the missing ones. Also, I don't know how to compute for the curved points of bezier curves and finding the right amount of t would be very computationally expensive so I don't implement it.

Here's the code how I draw the inner quartered circle and I think I can just reuse it.

void drawArc(int x, int y,
             int startAngle, int endAngle,
             uint32_t radiusX, uint32_t radiusY,
             int border_x, int border_y,
             const rgb color,
             const rgb bcX, const rgb bcY,
             uint8_t opacity)
{
    if (radiusX <= 0 || radiusY <= 0) return;

    static constexpr float DTR = 3.14159 / 180;

    float cx, cy;
    int step;

    static std::vector<float> verts;
    static std::vector<uint8_t> colors;

    if (startAngle < endAngle)
    {
        step = +1;
        ++ endAngle;
    } else
    {
        step = -1;
        -- endAngle;
    }

    verts.clear();
    colors.clear();

    verts.push_back(x);
    verts.push_back(y);

    colors.push_back(color[R]);
    colors.push_back(color[G]);
    colors.push_back(color[B]);
    colors.push_back(opacity);

    while (startAngle != endAngle)
    {
        cx = cos(DTR * startAngle) * radiusX;
        cy = sin(DTR * startAngle) * radiusY;

        verts.push_back(x + cx);
        verts.push_back(y - cy);

        colors.push_back(color[R]);
        colors.push_back(color[G]);
        colors.push_back(color[B]);
        colors.push_back(opacity);

        startAngle += step;
    }

    drawElements(GL_POLYGON, sizeof(arcIndices) / sizeof(arcIndices[0]), GL_FLOAT,
                 &verts[0], &colors[0], &arcIndices[0]);

    if (border_x != 0 || border_y != 0)
    {
        //remove (x, y)
        verts.erase(verts.begin(), verts.begin() + 2);

//        float px, py;
//
//        px = *(verts.begin() + 0);
//        py = *(verts.begin() + 1);
//
//        glPointSize(5);
//
//        glBegin(GL_POINTS);
//
//        glColor3ub(0,0,255);
//        glVertex2i(px, py);
//
//        px = *(verts.end() - 2);
//        py = *(verts.end() - 1);
//
//        glColor3ub(255,0,0);
//        glVertex2i(px , py);
//        glEnd();

        //attempting to reuse the edges
        //I think the last vertices are opposed
        //that's why I got a crossed out lines??
        for (int i = 0;i <= 90; ++i)
        {
            verts.push_back(verts[i + 0] + border_x);
            verts.push_back(verts[i + 1] + border_y);

            colors.push_back(bcX[R]);
            colors.push_back(bcX[G]);
            colors.push_back(bcX[B]);
            colors.push_back(opacity);
        }

        //91 = steps from 0-90 degree revolution
        //182 = 91 * 2
        unsigned int index[182 + 91 * 2];
        for (int i = 0;i < 182 + 91 * 2; ++i)
            index[i] = i;

        drawElements(GL_LINE_LOOP, verts.size() / 2, GL_FLOAT,
                    &verts[0], &colors[0], &index[0]);
    }

}

Edit:

Can't I just reuse the pre-calculated (x,y) before?

nooh

Sorry for too much use of pictures

The red dots are pre-calculated (x, y) I'm referring to and just append the next arc base on this.

I'm gonna render many of this kind so I need as efficient as possible(w/o too much use to trigo functions).

Update:

And here is the result I got from using stencil buffer as what Andon M. Coleman suggested:

nuuh

Btw, as you can see, I am trying to emulate my own UI using OpenGL :D

2

There are 2 answers

4
Andon M. Coleman On BEST ANSWER

You expressed an interest in seeing how this could be solved using the stencil buffer yesterday, so I am following up with some basic pseudo-code.

glClearStencil (0x0);
glClear        (GL_STENCIL_BUFFER_BIT);

glEnable       (GL_STENCIL_TEST);
glStencilFunc  (GL_ALWAYS, 0x0, 0x0);

// Add 1 to stencil buffer at every location the object to be bordered is visible
glStencilOp    (GL_KEEP, GL_KEEP, GL_INCR);

// Draw your grey object

// Only draw the red border where the grey object was never drawn (stencil = 0x0)
glStencilFunc  (GL_EQUAL, 0x0, 0xff);

// Draw your red quarter circles

glDisable     (GL_STENCIL_TEST);

Clearing the stencil buffer everytime you draw your outlined object is probably overkill. If you opt to clear the stencil buffer once per-frame instead, you can do some pretty interesting things. For instance, if you drew the outlines as a separate pass after all non-outlined shapes are drawn you could use this stencil buffer setup to outline the union (instead of including the intersection of objects as part of the drawn outline) of any overlapping objects.. this would allow you to construct more complicated shapes from your simple rounded rectangles.

Of course for this to work, your pixel format must have a stencil buffer. I will have to leave that part up to you, because the process of setting that up is implementation specific.

3
genpfault On

GL_POLYGON is only for convex polygons.

Link together the vertices on your inner and outer radii to form quads/triangles:

partial torus

#include <GL/glut.h>
#include <cmath>

void Torus2d
    ( 
    float angle,            // starting angle in radians
    float length,           // length of arc in radians, >0
    float radius,           // inner radius, >0
    float width,            // width of torus, >0
    unsigned int samples    // number of circle samples, >=3
    )
{
    if( samples < 3 ) samples = 3;
    const float outer = radius + width;
    glBegin( GL_QUAD_STRIP );
    for( unsigned int i = 0; i <= samples; ++i )
    {
        float a = angle + ( i / (float)samples ) * length;
        glVertex2f( radius * cos( a ), radius * sin( a ) );
        glVertex2f( outer * cos( a ), outer * sin( a ) );
    }
    glEnd();
}

void display()
{
    glClear( GL_COLOR_BUFFER_BIT );

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    double w = glutGet( GLUT_WINDOW_WIDTH );
    double h = glutGet( GLUT_WINDOW_HEIGHT );
    double ar = w / h;
    glOrtho( -4 * ar, 4 * ar, -4, 4, -1, 1);

    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();

    glColor3ub( 255, 0, 0 );
    Torus2d( 0, 1.57079633, 2, 1, 20 );

    glutSwapBuffers();
}

int main( int argc, char **argv )
{
    glutInit( &argc, argv );
    glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
    glutInitWindowSize( 640, 480 );
    glutCreateWindow( "GLUT" );
    glutDisplayFunc( display );
    glutMainLoop();
    return 0;
}