c++openglmathgraphicstesselation

Plotting 2D map coordinates to OpenGL 3D projection


I’m trying to convert a 2D map created in my map editor to a 3D plotting with OpenGL. This is my map generated in my map editor:

Map editor

Those vertices are relative to my Cartesian origin world coordinate (top up of the picture) and I’m applying this formula to convert it to an OpenGL object coordinate:

World size: 800x600

x = (X / 800) -0.5
y = (Y / 600) -0.5

Getting this result:

(First object face)

−0.48625, 0.068333333
0.12625, 0.07
0.12875, −0.481666667
−0.4875, −0.486666667

Plotting this vertex buffer in OpenGL, I got a very weird result. So how can I get a 3D model from those vertex positions? Like this picture:

Box room

I’m rendering OpenGL in triangles mode and using this example as the start point: https://github.com/JoeyDeVries/LearnOpenGL/blob/master/src/1.getting_started/7.4.camera_class/camera_class.cpp

Using the conversion formula + the Earcut tessellation (https://github.com/mapbox/earcut.hpp), I've finally got this rectangle rendering correctly inside OpenGL. With two planes with just the Z-axis different, now the problem is how to render its laterals since Earcut just works with 2D coordinates...

Planes


Solution

  • If I get it right you got some planar 2D polygon and what to add some constant thickness to it (as a 3D mesh). That is doable fairly easily. As you correctly assumed you need to triangulate first. So you should have this input:

    1. table of points pnt[pnts]

      list of all points of your object.

    2. polygon pol[pols] (circumference of your object)

      just ordered list of points indexes referencing to table of points

    3. triangulation result fac[facs]

      ordered list of 3 point indexes representing all the triangles.

    Now to make a mesh from it we need to do this:

    1. copy all the points and extrude them by some translation.

      all this new points will be added to current pnt[pnts] table. Do not forget to remember original table size pnts0 as it will be needed later.

    2. copy/reverse the triangulation.

      The opposite side of the triangulated polygon will be the same just in reverse polygon winding. So just copy it to fac[facs] as new triangles in reverse index order ... Do not forget to add the original point table size to all the new faces. This will use the new points ... From the images of yours you got to this point already.

    3. create the missing side faces.

      For this we can exploit the original polygon. As we just copied the points then we know that pnt[3*i] is opposite to pnt[pnts0+3*i]. So we just create triangle faces joining the opposite edges of the polygon.

    Here small C++ example I busted for this right now:

    //---------------------------------------------------------------------------
    #include <vcl.h>
    #include <math.h>
    #pragma hdrstop
    #include "Unit1.h"
    #include "gl_simple.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    //---------------------------------------------------------------------------
    const int N=128;
    int pnts=6*3;                   // 3* number of points
    float pnt[N]=                   // x,y per each point
        {
        -0.5,-0.5,0.0,              //   6 ------ 9
        -0.4, 0.0,0.0,              //    +      +
        -0.5,+0.5,0.0,              //     3   12
        +0.5,+0.5,0.0,              //    +      +
        +0.4, 0.0,0.0,              //   0 ----- 15
        +0.5,-0.5,0.0,
        };
    int pol[N]={ 0,3,6,9,12,15 }, pols=6; // original polygon (3*pnt index), number of its vertexes
    int fac[N]=                     // triangulation result (3*pnt index)
        {
        0,3,15,
        3,12,15,
        3,6,12,
        6,9,12,
        }, facs=4*3;                // number of triangles*3
    //---------------------------------------------------------------------------
    void extrude(float dz)
        {
        int i,i0,pnts0=pnts;
        // copy and reverse triangulation
        for (i=0;i<facs;i++)
         fac[facs+facs-1-i]=fac[i]+pnts; facs+=facs;
        // duplicate points
        for (i=0;i<pnts;i++) pnt[pnts0+i]=pnt[i]; pnts+=pnts;
        // extrude points
        for (i=      2;i<pnts0;i+=3) pnt[i]-=dz;
        for (         ;i<pnts ;i+=3) pnt[i]+=dz;
        // side faces
        for (i0=pols-1,i=0;i<pols;i0=i,i++)
            {
            fac[facs]=pol[i ]+pnts0; facs++;
            fac[facs]=pol[i ];       facs++;
            fac[facs]=pol[i0];       facs++;
    
            fac[facs]=pol[i0]+pnts0; facs++;
            fac[facs]=pol[i ]+pnts0; facs++;
            fac[facs]=pol[i0];       facs++;
            }
        }
    //---------------------------------------------------------------------------
    void gl_draw()
        {
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        glDisable(GL_TEXTURE_2D);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        glEnable(GL_CULL_FACE);
        glFrontFace(GL_CCW);
        glEnable(GL_COLOR_MATERIAL);
    /*
        glPolygonMode(GL_FRONT,GL_FILL);
        glPolygonMode(GL_BACK,GL_LINE);
        glDisable(GL_CULL_FACE);
    */
    
        // set view
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTranslatef(0.0,0.0,-5.0);
        static float ang=0.0;
        glRotatef(ang,0.2,0.7,0.1); ang+=5.0; if (ang>=360.0) ang-=360.0;
    
        // render mesh
        float *p0,*p1,*p2,n[3],a[3],b[3],c;
        glColor3f(0.7,0.7,0.7);
    
        glBegin(GL_TRIANGLES);
        for (int i=0;i+3<=facs;i+=3)
            {
            // points
            p0=pnt+fac[i+0];
            p1=pnt+fac[i+1];
            p2=pnt+fac[i+2];
            // compute normal
            a[0]=p1[0]-p0[0]; a[1]=p1[1]-p0[1]; a[2]=p1[2]-p0[2];
            b[0]=p2[0]-p1[0]; b[1]=p2[1]-p1[1]; b[2]=p2[2]-p1[2];
            n[0]=(a[1]*b[2])-(a[2]*b[1]);
            n[1]=(a[2]*b[0])-(a[0]*b[2]);
            n[2]=(a[0]*b[1])-(a[1]*b[0]);
            c=1.0/sqrt((n[0]*n[0])+(n[1]*n[1])+(n[2]*n[2]));
            n[0]*=c; n[1]*=c; n[2]*=c;
            // render
            glNormal3fv(n);
            glVertex3fv(p0);
            glVertex3fv(p1);
            glVertex3fv(p2);
            }
        glEnd();
    
    //  glFlush();
        glFinish();
        SwapBuffers(hdc);
        }
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
        {
        // Init of program
        gl_init(Handle);    // init OpenGL
        extrude(0.2);
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormDestroy(TObject *Sender)
        {
        // Exit of program
        gl_exit();
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormPaint(TObject *Sender)
        {
        // repaint
        gl_draw();
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormResize(TObject *Sender)
        {
        // resize
        gl_resize(ClientWidth,ClientHeight);
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::tim_redrawTimer(TObject *Sender)
        {
        gl_draw();
        }
    //---------------------------------------------------------------------------
    

    It is VCL based so ignore all the VCL stuff and port the events you want/need and GL context stuff to your style of programing. The only important stuff here are:

    the tables pnt,fac,pol which holds the input and latter also output. The extrude(dz) will create the mesh (call it just once!) and gl_draw will render the tables as mesh (Using the old style GL api for simplicity).

    For the GL stuff I used my gl_simple.h which you can find in this related QA:

    Here is preview of the code above:

    preview

    the choppynes is due to my GIF capture the rendering is smooth. I used static allocation and on the run normal computation so the code is simple and easy to understand. Of coarse for the real deal you need to implement dynamic lists and VAO/VBO ... if you want good performance