OpenGL par_shapes.h

En este tutorial veremos un pequeña biblioteca llamada par_shapes.h la cual nos ayuda a generar diversas figuras geométricas 3D, está escrita en C99 y no requiere dependencia alguna, además de generar figuras también nos permite aplicarles transformaciones y realizar diversas operaciones sobre ellas, calcular sus normales, por ejemplo.

Para utilizar esta biblioteca solo necesitas el archivo par_shapes.h que puedes ubicar en github: https://github.com/prideout/par/blob/master/par_shapes.h cuando lo descarguemos solo debemos incluirlo en nuestro proyecto.   

image

Para crear una figura solo debemos llamar a la función par_shapes_create_XXX donde XXX es el nombre de la figura que deseamos generar, disponemos de las siguiente: cylinder, torus, parametric_sphere, subdivided_sphere, klein_bottle, trefoil_knot, hemisphere, plane, icosahedron, dodecahedron, octahedron, tetrahedron, cube, disk, rock, lsystem.

Puedes ver al archivo par_shapes.h para ver una descripción de cada una de estas funciones y los distintos parámetros de las mismas.

Para crear la figura llamamos a la función correspondiente, esta nos devolverá un puntero a una estructura par_shapes_mesh que contiene toda la información requerida para dibujar la figura, al finalizar liberamos la memoria utilizando par_shapes_free_mesh(). 

par_shapes_mesh* m = par_shapes_create_trefoil_knot(30, 100, 0.8);

par_shapes_free_mesh(m);

La estructura par_shapes_mesh se define de la siguiente manera:

typedef struct par_shapes_mesh_s {
    float* points;           // Flat list of 3-tuples (X Y Z X Y Z...)
    int npoints;             // Number of points
    PAR_SHAPES_T* triangles; // Flat list of 3-tuples (I J K I J K...)
    int ntriangles;          // Number of triangles
    float* normals;          // Optional list of 3-tuples (X Y Z X Y Z...)
    float* tcoords;          // Optional list of 2-tuples (U V U V U V...)
} par_shapes_mesh;
  • points: contiene una lista de elementos tipo float que representan la posición de los vértices que componen la figura, agrupados de la forma (X, Y, Z).
  • npoints: cantidad de vértices, un grupo de 3 float  (X, Y, Z) es un vértice.
  • triangles: lista de índices de los vértices requeridos para formar una cara triangular de la figura, agrupados de tres en tres.  
  • ntriangles: cantidad de caras triangulares.
  • normals: vectores normales de la figura, puede ser nulo.
  • tcoords: coordenadas UV para texturas, puede ser nulo.

Render en OpenGL Moderno


par_shapes_mesh* shape = par_shapes_create_trefoil_knot(30, 100, 0.8);

glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

glGenBuffers(3, buffer);

glBindBuffer(GL_ARRAY_BUFFER, buffer[0]);
glBufferData(GL_ARRAY_BUFFER, shape->npoints * 3 * sizeof(float), shape->points, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, buffer[1]);
glBufferData(GL_ARRAY_BUFFER, shape->npoints * 3 * sizeof(float), shape->normals, GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(1);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[2]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, shape->ntriangles * 3 * sizeof(PAR_SHAPES_T), shape->triangles, GL_STATIC_DRAW);

glBindVertexArray(0);

par_shapes_free_mesh(shape);

Este es el código necesario para dibujar la figura generada usando la API OpenGL Moderna, solo creamos los buffers y les pasamos la información necesaria.

Calcular Normales de Cara

Algunas de las figuras no poseen la información de normales, por lo veremos como calcularla, par_shape cuenta con la función  par_shapes_compute_normals para calcular las normales de vértices, para utilizarla solo hay que indicarle el par_shape_mesh al cual deseamos calcular sus normales, si deseamos calcular las normales de cara debemos hacerlo manualmente, del siguiente modo: 

par_shapes_mesh* shape = par_shapes_create_dodecahedron();

std::vector<glm::vec3> position, normal;

PAR_SHAPES_T const* triangle = shape->triangles;

for (int f = 0; f < shape->ntriangles; f++, triangle += 3) {
    float const* pa = shape->points + 3 * triangle[0];
    float const* pb = shape->points + 3 * triangle[1];
    float const* pc = shape->points + 3 * triangle[2];

    glm::vec3 p0(pa[0], pa[1], pa[2]);
    glm::vec3 p1(pb[0], pb[1], pb[2]);
    glm::vec3 p2(pc[0], pc[1], pc[2]);

    position.push_back(p0);
    position.push_back(p1);
    position.push_back(p2);

    glm::vec3 n = glm::normalize(glm::cross(p1 - p0, p2 - p0));

    normal.push_back(n);
    normal.push_back(n);
    normal.push_back(n);
}

count = position.size();

glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

glGenBuffers(3, buffer);

glBindBuffer(GL_ARRAY_BUFFER, buffer[0]);
glBufferData(GL_ARRAY_BUFFER, position.size() * sizeof(glm::vec3), position.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, buffer[1]);
glBufferData(GL_ARRAY_BUFFER, normal.size() * sizeof(glm::vec3), normal.data(), GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
glEnableVertexAttribArray(1);

glBindVertexArray(0);

par_shapes_free_mesh(shape);

Si deseamos aplicar alguna transformación a la figura simplemente llamamos a la función con los parámetros correspondientes, ejemplo:

par_shapes_rotate(shape, PAR_PI / 5.0, (float[]){0, 1, 0});
par_shapes_translate(shape, 1, 0, 0.5);
par_shapes_scale(shape, 1.2, 1.2, 1.2);

Para finalizar veremos la función par_shapes_export(shape, “/modelo/shape.obj”) con ella podremos almacenar en disco la figura deseada, indicamos el par_shape_mesh y la ruta donde deseamos crear el archivo, la figura se almacena el formato Wavefront OBJ, puedes ver como cargar modelos 3D en este formato en el tutorial, OBJ Loader con OpenGL.  

Proyecto en GitHub: OpenGL Par Shapes

Comentarios

Entradas populares de este blog

Conectar SQL Server con Java

Entrenar OpenCV en Detección de Objetos

Procesamiento de imágenes en OpenCV

Acceso a la webcam con OpenCV

Conociendo la clase cv::Mat de OpenCV