Escuela Técnica Superior de Ingeniería

 

Grado en Ingeniería Informática

Animación por Ordenador

Curso 2025/2026

 

Práctica 5a

Presentación de figuras geométricas de 3D

 

Objetivo

 

Modificar el proyecto Project4c para sustituir el triángulo por objetos de tres dimensiones. Se incluye en el Vertex Shader una matriz de transformación para poder generar giros en la figura, se modifica la plantilla de descriptores para introducir la matriz y se modifican los eventos de teclado para asociar las flechas a transformaciones de giro.

 

 

Shaders

 

Para modelar escenas en tres dimensiones, los objetos que forman parte de la escena se almacenan en forma de malla de vértices (mesh). Los atributos de los vértices contienen, al menos, la posición de cada vértice en el sistema de coordenadas propio del objeto. Para calcular la posición que ocupa cada vértice en la imagen a generar hay que transformar las coordenadas de los vértices a coordenadas homogéneas. Esta transformación se realiza en tres pasos. En primer lugar se transforman las posiciones de los vértices al sistema de coordenadas de la escena, multipicando por una matriz conocida como Model. En segundo lugar se transforman desde el sistema de coordenadas de la escena al sistema de coordenadas del observador multiplicando por una matriz denominada View. En tercer lugar se transforman del sistema de coordenadas del observador al sistema de coordenadas homogéneas multiplicando por una matriz denominada Projection. Las tres transformaciones suponen multiplicar el vector de posición del vértice por tres matrices de transformación. Se suele multiplicar en primer lugar las tres matrices entre sí para obtener una única transformación completa que se representa mediante una matriz Model-View-Projection o MVP.

 Para representar un objeto 3D se va a modificar el Vertex Shader para definir un único atributo, llamado inPosition, que almacena la posición del vértice como un vector de tres dimensiones (x,y,z). También se modifican las variables uniformes para definir en este caso un conjunto de descriptores formado por un único valor, llamado MVP, que almacena la matriz de transformación. El método main() calcula la variable de salida gl_Position como el producto entre el vector de posición y la matriz de transformación. El color asociado a cada vértice se ha definido como rojo.

#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(binding = 0) uniform UniformBufferObject {
  mat4 MVP;
} ubo;

layout(location = 0) in vec3 inPosition;

layout(location = 0) out vec3 fragColor;

void main() 
{
  gl_Position = ubo.MVP*vec4(inPosition, 1.0);
  fragColor = vec3(1.0,0.0,0.0);
}

El Fragment Shader sigue recibiendo como entrada el color de cada píxel y generándolo como salida. En este caso el color será siempre rojo. Realmente no es necesario utilizar la variable fragColor ya que el color se podría haber fijado directamente en el shader.

#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(location = 0) in vec3 fragColor;

layout(location = 0) out vec4 outColor;

void main() {
  outColor = vec4(fragColor, 1.0);
}

 

 

Las estructuras GEVertex y GETransform

 

La información que leen los shaders de la memoria del dispositivo son los atributos de los vértices y las variables uniformes.

Los atributos asociados a los vértices se van a almacenar en estructuras de tipo GEVertex. En este caso la estructura solo está formada por un campo pos que almacena un vector con las tres coordenadas (x,y,z) de la posición del vértice. Estos campos corresponden a las variables de entrada del Vertex Shader.

#pragma once

#include <glm\common.hpp>

//
// ESTRUCTURA: GEVertex
//
// DESCRIPCIÓN: Estructura que describe los atributos de un vértice
//
typedef struct 
{
  glm::vec3 pos;
} GEVertex;

Las variables uniformes se almacenan en estructuras de tipo GETransform. Esta estructura corresponde al bloque de información que se almacenará en el buffer vinculado a la variable uniforme ubo introducida en el Vertex Shader. En este caso la estructura contiene solamente la matriz MVP.

#pragma once

#include <glm/glm.hpp>

//
// ESTRUCTURA: GETransform
//
// DESCRIPCIÓN: Estructura que almacena los datos necesarios para girar las figuras
//
typedef struct
{
  glm::mat4 MVP;
} CATransform;

 

 

Modificaciones de la clase GEScene

 

La clase GEScene se modifica para describir el nuevo modelo 3D a representar, que en este caso estará formado por un conjunto de figuras geométricas. La clase almacena las matrices model, view y projection que se utilizarán para transformar las coordenadas locales de la figura en las coordenadas homogeneas. A partir de estas tres matrices se generará la matriz MVP que se leerá en el pipeline de renderizado como una variable uniforme.

#pragma once

#include "GEGraphicsContext.h"
#include "GEDrawingContext.h"
#include "GECommandContext.h"
#include "GERenderingContext.h"

#include "GEFigure.h"
#include <vulkan/vulkan.h>
#include <glm/glm.hpp>
#include <vector>

//
// CLASE: GEScene
//
// DESCRIPCIÓN: Clase que describe una escena
//
class GEScene
{
private:
  GERenderingContext* rc;
  GEFigure* figure[8];
  glm::mat4 model;
  glm::mat4 view;
  glm::mat4 projection;
  int figureIndex;

public:
  GEScene(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc);
  void destroy(GEGraphicsContext* gc);
  void recreate(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc);
  void update(GEGraphicsContext* gc, uint32_t index);
  void key_pressed(int key);
  void aspect_ratio(double aspect);

private:
  void fillCommandBuffers(std::vector<VkCommandBuffer> commandBuffers);
  GEPipelineConfig* createPipelineConfig(VkExtent2D extent);
};

Los métodos de la clase tienen la siguiente fuincionalidad:

  • GEScene(): El constructor de la clase crea el contexto de renderizado, crea e inicializa las figuras geométricas a mostrar y asigna el valor inicial a las matrices model, view y projection. La matriz model se encargará de representar el giro del cubo y se inicializa a la matriz unidad. La matriz view representa la posición del cubo respecto de la cámara y se fija como un desplazamiento en la dirección negativa del eje Z. La matriz projection se asigna mediante el método aspect_ratio().

  • destroy(): Destruye las figuras geométricas y el contexto de renderizado.

  • recreate(): Reconstruye el contexto de renderizado y rellena de nuevo los buffers de comandos.

  • fillCommandBuffers(): Añade a los buffesr de comandos los comandos de dibujo de la figura activa.

  • update(): Es el responsable de actualizar los valores del modelo y consiste en calcular y actualizar en memoria el valor de la matriz MVP que se calcula como el producto de las matrices model, view y projection.

  • key_pressed(): Calcula la respuesta a los eventos de teclado, que consiste en modificar la matriz model mediante giros asociados a las flechas del teclado. Estos matrices de giro se calculan fácilmente gracias a la función glm::rotate()

  • aspect_ratio(): La matriz projection se crea y modifica en el método aspect_ratio(). Para generar la matriz se utiliza el método glm::perspective() que genera una matriz de proyección en perspectiva a partir de los valores del campo de visíón sobre el eje Y (parámetro fov que se ha fijado en 30 grados), la relación de aspecto entre el eje X y el eje Y (que se calcula a partir del tamaño de la ventana), la distancia del plano más cercano (parámetro near) y la distancia al plano más lejano (parámetro far).

  • createPipelineConfig(): Genera la configuración del pipeline de renderizado. En este caso define un único atributo de tipo vec3 asociado a la posición de cada vértice y un único descriptor asociado a la variable uniforme Transform.

El código de los métodos es el siguiente.

#include "GEScene.h"

#include "GECube.h"
#include "GEPyramid.h"
#include "GECone.h"
#include "GECylinder.h"
#include "GESphere.h"
#include "GEDisk.h"
#include "GETorus.h"
#include "GEIcosahedron.h"
#include "GETransform.h"
#include <windows.h>
#include "resource.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

//
// FUNCIÓN: GEScene::GEScene(GEGraphicsContext* gc, GEDrawingContext* dc)
//
// PROPÓSITO: Crea la escena
//
GEScene::GEScene(GEGraphicsContext* gc, GEDrawingContext* dc, GECommandContext* cc)
{
  VkExtent2D extent = dc->getExtent();
  double aspect = (double)extent.width / (double)extent.height;
  aspect_ratio(aspect);

  GEPipelineConfig* config = createPipelineConfig(dc->getExtent());
  rc = new GERenderingContext(gc, dc, config);

  figure[0] = new GECube(10.0f);
  figure[0]->initialize(gc, rc);

  figure[1] = new GEPyramid(10.0f);
  figure[1]->initialize(gc, rc);

  figure[2] = new GECone(5, 20, 10.0f, 10.0f);
  figure[2]->initialize(gc, rc);

  figure[3] = new GECylinder(20, 20, 10.0f, 10.0f);
  figure[3]->initialize(gc, rc);

  figure[4] = new GESphere(20, 20, 10.0f);
  figure[4]->initialize(gc, rc);

  figure[5] = new GEDisk(5, 20, 7.0f, 15.0f);
  figure[5]->initialize(gc, rc);

  figure[6] = new GETorus(10, 20, 5.0f, 10.0f);
  figure[6]->initialize(gc, rc);

  figure[7] = new GEIcosahedron(10.0f); 
  figure[7]->initialize(gc, rc);

  fillCommandBuffers(cc->commandBuffers);

  model = glm::mat4(1.0f);
  view = glm::mat4(1.0f);
  view = glm::translate(view, glm::vec3(0.0f, 0.0f, -100.0f));
  figureIndex = 0;
}

//
// FUNCIÓN: GEScene::destroy(GEGraphicsContext* gc)
//
// PROPÓSITO: Reconstruye los componentes gráficos de la escena
//
void GEScene::destroy(GEGraphicsContext* gc)
{
  rc->destroy(gc);
  delete rc;

  for (int i = 0; i < 3; i++)
  {
    figure[i]->destroy(gc); 
    delete figure[i];
  }
}

//
// FUNCIÓN: GEScene::recreate(...)
//
// PROPÓSITO: Reconstruye los componentes gráficos de la escena
//
void GEScene::recreate(GEGraphicsContext* gc, 
                       GEDrawingContext* dc, 
                       GECommandContext* cc)
{
  rc->destroy(gc);
  GEPipelineConfig* config = createPipelineConfig(dc->getExtent());
  this->rc = new GERenderingContext(gc, dc, config);
  fillCommandBuffers(cc->commandBuffers);
}

//
// FUNCIÓN: GEScene::update(GEGraphicsContext* gc, uint32_t index)
//
// PROPÓSITO: Actualiza la información para generar la imagen 
//
void GEScene::update(GEGraphicsContext* gc, uint32_t index)
{
  GETransform ubo = {};
  ubo.MVP = projection * view * model;
  figure[figureIndex]->update(gc, index, ubo);
}

//
// FUNCIÓN: GEScene::key_pressed(int)
//
// PROPÓSITO: Respuesta a acciones de teclado
//
void GEScene::key_pressed(int key)
{
  switch (key)
  {
  case GLFW_KEY_UP:
    model = glm::rotate(model, glm::radians(5.0f), glm::vec3(1.0f, 0.0f, 0.0f));
    break;
  case GLFW_KEY_DOWN:
    model = glm::rotate(model, glm::radians(-5.0f), glm::vec3(1.0f, 0.0f, 0.0f));
    break;
  case GLFW_KEY_LEFT:
    model = glm::rotate(model, glm::radians(5.0f), glm::vec3(0.0f, 1.0f, 0.0f));
    break;
  case GLFW_KEY_RIGHT:
    model = glm::rotate(model, glm::radians(-5.0f), glm::vec3(0.0f, 1.0f, 0.0f));
    break;
  case GLFW_KEY_F:
    figureIndex = (figureIndex + 1) % 8;
    break;
  }
}

//
// FUNCIÓN: GEScene::aspect_ratio(double)
//
// PROPÓSITO: Modifica la relación anchura/altura del modelo
//
void GEScene::aspect_ratio(double aspect)
{
  constexpr double fov = glm::radians(30.0f);
  double sin_fov = sin(fov);
  double cos_fov = cos(fov);
  float wHeight = (float)(sin_fov * 0.2 / cos_fov);
  float wWidth = (float)(wHeight * aspect);

  projection = glm::perspective((float)fov, (float)aspect, 0.2f, 400.0f);
  projection[1][1] *= -1.0f;
}

//
// FUNCIÓN: CAScene::fillCommandBuffers(std::vector<VkCommandBuffer> commandBuffers)
//
// PROPÓSITO: Añade los comandos de renderizado al command buffer
//
void GEScene::fillCommandBuffers(std::vector<VkCommandBuffer> commandBuffers)
{
  rc->startFillingCommandBuffers(commandBuffers);
  for (int i = 0; i < commandBuffers.size(); i++) 
  {
    figure[figureIndex]->addCommands(commandBuffers[i], rc->pipelineLayout, i);
  }
  rc->endFillingCommandBuffers(commandBuffers);
}

//
// FUNCIÓN: GEScene::createPipelineConfig()
//
// PROPÓSITO: Obtiene la configuración del pipeline de renderizado
//
GEPipelineConfig* GEScene::createPipelineConfig(VkExtent2D extent)
{
  GEPipelineConfig* config = new GEPipelineConfig();
  config->vertex_shader = IDR_HTML1;
  config->fragment_shader = IDR_HTML2;

  config->attrStride = sizeof(GEVertex);
  config->attrOffsets.resize(1);
  config->attrOffsets[0] = offsetof(GEVertex, pos);
  config->attrFormats.resize(1);
  config->attrFormats[0] = VK_FORMAT_R32G32B32_SFLOAT;

  config->descriptorTypes.resize(1);
  config->descriptorTypes[0] = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
  config->descriptorStages.resize(1);
  config->descriptorStages[1] = VK_SHADER_STAGE_VERTEX_BIT;

  config->depthTestEnable = VK_TRUE;
  config->cullMode = VK_CULL_MODE_BACK_BIT;
  config->extent = extent;

  return config;
}

 

 

Modificaciones de las clase GEApplication y GERenderingContext

 

En la realización de este proyecto se han tenido que incluir pequeñas modificaciones en las clases GEApplication y GERenderingContext.

Con respecto a la clase GEApplication, se ha modificado el método keyCallback() para reconstruir la escena tras cambiar de figura al pulsar la tecla 'F' y el método resize() para modificar la relación de aspecto de la escena al modificar el tamaño de la ventana.

//
// FUNCIÓN: GEApplication::keyCallback(...)
//
// PROPÓSITO: Respuesta a un evento de teclado sobre la aplicación
//
void GEApplication::keyCallback(GLFWwindow* window, int key, int scancode, 
                                                             int action, int mods)
{
  GEApplication* app = (GEApplication*)glfwGetWindowUserPointer(window);
  if (action == GLFW_PRESS || action == GLFW_REPEAT)
  {
    if (key == GLFW_KEY_F12) app->swapFullScreen();
    else app->scene->key_pressed(key);

    if (key == GLFW_KEY_F) app->resize();
  }
}

//
// FUNCIÓN: GEApplication::resize()
//
// PROPÓSITO: Reconstruye los objetos con el nuevo tamaño de ventana
//
void GEApplication::resize()
{
  if (!windowPos.fullScreen)
  {
    glfwGetWindowSize(window, &windowPos.width, &windowPos.height);
    glfwGetWindowPos(window, &windowPos.Xpos, &windowPos.Ypos);
  }

  dc->recreate(gc, windowPos);
  cc->destroy(gc);
  delete cc;

  this->cc = new GECommandContext(this->gc, this->dc->getImageCount());

  scene->recreate(gc, dc, cc);

  double aspect;
  if (!windowPos.fullScreen) 
  {
    aspect = (double)this->windowPos.width / (double)this->windowPos.height;
  }
  else
  {
    aspect=(double)this->windowPos.screenWidth/(double)this->windowPos.screenHeight;
  }
  this->scene->aspect_ratio(aspect);
}

Por su parte, en la clase GERenderingContext se ha modificado el método createPipelineRasterizationStateCreateInfo() para que el campo polygonMode active el dibujo en modo arista (VK_POLYGON_MODE_LINE) en vez de modo relleno (VK_POLYGON_MODE_FILL).

//
// FUNCIÓN: GERenderingContext::createPipelineRasterizationStateCreateInfo()
//
// PROPÓSITO: Crea la información de la etapa de rasterización
//
void GERenderingContext::createPipelineRasterizationStateCreateInfo(
                               GEPipelineConfig* config, 
                               VkPipelineRasterizationStateCreateInfo* rasterizer)
{
  *rasterizer = {};
  rasterizer->sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
  rasterizer->depthClampEnable = VK_FALSE;
  rasterizer->rasterizerDiscardEnable = VK_FALSE;
  rasterizer->polygonMode = VK_POLYGON_MODE_LINE;
  rasterizer->lineWidth = 1.0f;
  rasterizer->cullMode = config->cullMode;
  rasterizer->frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
  rasterizer->depthBiasEnable = VK_FALSE;
}

 

 

La clase GEFigure

 

Para definir figuras en 3D se va a transformar la clase GEFigure en una clase abstracta que define los campos vertices e indices como campos protegidos para que las clases hijas se limiten a darle contenido a estos campos y aprovechar toda la funcionalidad ya creada para esta clase.

#pragma once

#include "GEGraphicsContext.h"
#include "GERenderingContext.h"
#include "GEVertex.h"
#include "GETransform.h"
#include "GEVertexBuffer.h"
#include "GEIndexBuffer.h"
#include "GEUniformBuffer.h"
#include "GEDescriptorSet.h"
#include <vector>

class GERenderingContext;
class GEDescriptorSet;

//
// CLASE: GEFigure
//
// DESCRIPCIÓN: Clase que describe una figura formada por una malla de vértices
//
class GEFigure
{
protected:
  std::vector<GEVertex> vertices;
  std::vector<uint16_t> indices;

public:
  void initialize(GEGraphicsContext* gc, GERenderingContext* rc);
  void destroy(GEGraphicsContext* gc);
  void addCommands(VkCommandBuffer commandBuffer, VkPipelineLayout pipelineLayout, 
                                                                       int index);
  void update(GEGraphicsContext* gc, uint32_t index, GETransform transform);

private:
  GEVertexBuffer* vbo;
  GEIndexBuffer* ibo;
  GEUniformBuffer* ubo;
  GEDescriptorSet* dset;
};

La única modificación respecto a la versión anterior de esta clase es que vertices e indices han pasado de ser constantes definidas en el exterior de la clase a ser campos internos de la clase. El constructor de la clase se ha redefinido como método initialize().

 

 

La clase GECube

 

El modelo del cubo se define en la clase GECube. El argumento s del constructor contiene el tamaño de la mitad del lado del cubo.

#pragma once

#include "GEFigure.h"

class GECube : public GEFigure
{
public:
  GECube(float s);
};

La clase hereda toda su funcionalidad de GEFigure y se limita a asignar el valor de los campos protegidos vertices e indices.

#include "GECube.h"

GECube::GECube(float s)
{
  vertices = {
  {{ +s, +s, +s }},  // A0 // Positive X
  {{ +s, -s, +s }},   // D0  
  {{ +s, -s, -s }}, // D1  
  {{ +s, +s, -s }}, // A1  

  {{ -s, +s, +s }}, // B0 // Positive Y
  {{ +s, +s, +s }}, // A0 
  {{ +s, +s, -s }}, // A1 
  {{ -s, +s, -s }}, // B1 

  {{ -s, -s, +s }}, // C0 // Negative X
  {{ -s, +s, +s }}, // B0 
  {{ -s, +s, -s }}, // B1 
  {{ -s, -s, -s }}, // C1

  {{ +s, -s, +s }}, // D0 // Negative Y
  {{ -s, -s, +s }}, // C0 
  {{ -s, -s, -s }}, // C1
  {{ +s, -s, -s }}, // D1  

  {{ +s, +s, +s }}, // A0 // Positive Z
  {{ -s, +s, +s }}, // B0 
  {{ -s, -s, +s }}, // C0 
  {{ +s, -s, +s }}, // D0  

  {{ +s, +s, -s }}, // A1 // Negative Z
  {{ +s, -s, -s }}, // D1  
  {{ -s, -s, -s }}, // C1 
  {{ -s, +s, -s }}  // B1 
  };

  indices = {  // Array of indexes
    0, 1, 2,
    0, 2, 3,
    4, 5, 6,
    4, 6, 7,
    8, 9, 10,
    8, 10, 11,
    12, 13, 14,
    12, 14, 15,
    16, 17, 18,
    16, 18, 19,
    20, 21, 22,
    20, 22, 23
  };
}

El aspecto de la figura es el siguiente.

Captura

 

 

La clase GEPyramid

 

La clase GEPyramid describe una pirámide de base cuadrada. La altura de la pirámide es 2·s y el lado de la base es también 2·s siendo s el argumento del constructor.

#pragma once

#include "GEFigure.h"

class GEPyramid: public GEFigure
{
public:
  GEPyramid(float s);
};

La clase hereda toda su funcionalidad de GEFigure y se limita a asignar el valor de los campos protegidos vertices e indices.

#include "GEPyramid.h"

GEPyramid::GEPyramid(float s)
{
  vertices = {
    {{ 0.0f, 0.0f, s }}, // E // Positive X
    {{ s, -s, -s }},     // B  
    {{ s, s, -s }},      // A

    {{ 0.0f, 0.0f, s }}, // E // Positive Y
    {{ s, s, -s }},      // A
    {{ -s, s, -s }},     // D

    {{ 0.0f, 0.0f, s }}, // E // Negative X
    {{ -s, s, -s }},     // D
    {{ -s, -s, -s }},    // C

    {{ 0.0f, 0.0f, s }}, // E // Negative Y
    {{ -s, -s, -s }},    // C
    {{ s, -s, -s }},     // B  

    {{ s, s, -s }},      // A // Negative Z
    {{ s, -s, -s }},     // B  
    {{ -s, -s, -s }},    // C
    {{ -s, s, -s }}     // D
  };

  indices = {  // Array of indexes
    0, 1, 2,
    3, 4, 5,
    6, 7, 8,
    9, 10, 11,
    12, 13, 14,
    12, 14, 15
  };
}

El aspecto de la figura es el siguiente.

Captura

 

 

La clase GECone

 

La clase GECone define un cono. El argumento p indica el número de trozos en los que se divide el cono. El argumento m es el número de puntos utilizado para generar cada circunferencia. El argumento r es el radio de la base del cono. La altura del cono es 2·h.

#pragma once

#include "GEFigure.h"

class GECone : public GEFigure
{
public:
  GECone(int p, int m, float r, float l);
};

El código de la clase es el siguiente.

#include "GECone.h"

GECone::GECone(GLint p, GLint m, GLfloat h, GLfloat r)
{
  int numFaces = 2 * p * m;     // Number of faces
  int numVertices = (p + 1)*m + 2;  // Number of vertices

  vertices.resize(numVertices);
  indices.resize(numFaces * 3);

  double module = sqrt(4 * h*h + r * r);
  double xyN = (float)(2 * h / module);
  double zN = (float)(r / module);

  int verticesIndex = 0;

  // Centro de la base
  vertices[0] = { { 0.0f, 0.0f, -h} };
  verticesIndex ++;

  // Vértices de la base
  for (int i = 0; i < m; i++)
  {
    float x = (float)cos(glm::radians(360.0f * i / m));
    float y = -(float)sin(glm::radians(360.0f * i / m));
    vertices[verticesIndex] = { {x * r , y * r, -h} };
    verticesIndex ++;
  }

  // Extremo del cono
  vertices[verticesIndex] = { {0.0f, 0.0f, h} };
  verticesIndex ++;

  // Vértices de los lados
  for (int i = 1; i <= p; i++)
  {
    float xy = i * r / p;
    float z = h - 2 * i*h / p;
    for (int j = 0; j < m; j++)
    {
      float xN = (float)cos(glm::radians(360.0f * j / m));
      float yN = (float)sin(glm::radians(360.0f * j / m));
      float x = (float)(xN*xy);
      float y = (float)(yN*xy);
      vertices[verticesIndex] = { { x, y, z} };
      verticesIndex ++;
    }
  }

  int indicesIndex = 0;
  // Base
  for (int i = 0; i < m - 1; i++)
  {
    indices[indicesIndex] = 0;
    indices[indicesIndex + 1] = i + 1;
    indices[indicesIndex + 2] = i + 2;
    indicesIndex += 3;
  }

  indices[indicesIndex] = 0;
  indices[indicesIndex + 1] = m;
  indices[indicesIndex + 2] = 1;
  indicesIndex += 3;

  // Extremo
  for (int i = 0; i < m - 1; i++)
  {
    indices[indicesIndex] = m + 1;
    indices[indicesIndex + 1] = m + 2 + i;
    indices[indicesIndex + 2] = m + 3 + i;
    indicesIndex += 3;
  }
  indices[indicesIndex] = m + 1;
  indices[indicesIndex + 1] = 2 * m + 1;
  indices[indicesIndex + 2] = m + 2;
  indicesIndex += 3;

  // Lados
  for (int j = 1; j < p; j++)
  {
    for (int i = 0; i < m - 1; i++)
    {
      indices[indicesIndex] = j * m + 2 + i;
      indices[indicesIndex + 1] = (j + 1)*m + 2 + i;
      indices[indicesIndex + 2] = (j + 1)*m + 3 + i;
      indicesIndex += 3;

      indices[indicesIndex] = j * m + 2 + i;
      indices[indicesIndex + 1] = (j + 1)*m + 3 + i;
      indices[indicesIndex + 2] = j * m + 3 + i;
      indicesIndex += 3;
    }

    indices[indicesIndex] = (j + 1)*m + 1;
    indices[indicesIndex + 1] = (j + 2)*m + 1;
    indices[indicesIndex + 2] = (j + 1)*m + 2;
    indicesIndex += 3;

    indices[indicesIndex] = (j + 1)*m + 1;
    indices[indicesIndex + 1] = (j + 1)*m + 2;
    indices[indicesIndex + 2] = j * m + 2;
    indicesIndex += 3;
  }
}

El aspecto de la figura es el siguiente.

Captura

 

 

La clase GECylinder

 

El modelo del cilindro se define en la clase GECylinder. El argumento p indica el níumero de tambores del cilindro. El argumento m es el número de puntos utilizados para dibujar cada circunferencia. El argumento r es el radio del cilindro. La altura del cilindro es 2·l.

#pragma once

#include "GEFigure.h"

class GECylinder : public GEFigure
{
public:
  GECylinder(int p, int m, float r, float l);
};

El código de la clase es el siguiente.

#include "GECylinder.h"

GECylinder::GECylinder(GLint p, GLint m, GLfloat r, GLfloat l)
{
  int numFaces = 2 * m * (p + 1);     // Number of faces
  int numVertices = (m + 1)*(p + 3);  // Number of vertices

  vertices.resize(numVertices);
  indices.resize(numFaces * 3);

  int verticesIndex = 0;
  int indicesIndex = 0;

  /* northern polar cap*/
  vertices[0] = { {0.0f, 0.0f, l } };
  verticesIndex++;

  for (int j = 0; j < m; j++)
  {
    float mCos = (float)cos(glm::radians(360.0f * j / m));
    float mSin = (float)sin(glm::radians(360.0f * j / m));
    vertices[verticesIndex] = { { mCos * r, mSin * r, l} };
    verticesIndex++;

    indices[indicesIndex] = 0; // center
    indices[indicesIndex + 1] = j + 1;
    indices[indicesIndex + 2] = (j + 2 > m ? 1 : j + 2);
    indicesIndex += 3;
  }

  /* southern polar cap*/
  vertices[verticesIndex] = { { 0.0f, 0.0f, -l} };
  verticesIndex ++;

  for (int j = 0; j < m; j++)
  {
    float mCos = (float)cos(glm::radians(360.0f * j / m));
    float mSin = (float)sin(glm::radians(360.0f * j / m));
    vertices[verticesIndex] = {{ mCos * r, -mSin * r, -l }};
    verticesIndex ++;

    indices[indicesIndex] = m + 1; // center
    indices[indicesIndex + 1] = j + m + 2;
    indices[indicesIndex + 2] = (j + 2 > m ? m + 2 : j + m + 3);
    indicesIndex += 3;
  }

  /* body */
  for (int i = 0; i <= p; i++)
  {
    for (int j = 0; j <= m; j++)
    {
      float mCos = (float)cos(glm::radians(360.0f * j / m));
      float mSin = (float)sin(glm::radians(360.0f * j / m));
      vertices[verticesIndex] = { { mCos * r, mSin * r, l - 2 * l*i / p} };
      verticesIndex ++;
    }
  }

  int base = 2 * m + 2;
  for (int i = 0; i < p; i++)
  {
    for (int j = 0; j < m; j++)
    {
      indices[indicesIndex] = base + (m + 1)*i + j;
      indices[indicesIndex + 1] = base + (m + 1)*(i + 1) + j;
      indices[indicesIndex + 2] = base + (m + 1)*(i + 1) + j + 1;
      indicesIndex += 3;

      indices[indicesIndex] = base + (m + 1)*i + j;
      indices[indicesIndex + 1] = base + (m + 1)*(i + 1) + j + 1;
      indices[indicesIndex + 2] = base + (m + 1)*i + j + 1;
      indicesIndex += 3;
    }
  }
}

El aspecto de la figura es el siguiente.

Captura

 

 

La clase GESphere

 

La clase GESphere describe una esfera. El argumento p indica el níumero paralelos de la esfera. El argumento m es el número de meridianos. El argumento r coontiene el radio de la esfera.

#pragma once

#include "GEFigure.h"

class GESphere : public GEFigure
{
public:
  GESphere(int p, int m, float r);
};

A continuación se muestra el código de la clase.

#include "GESphere.h"

GESphere::GESphere(GLint p, GLint m, GLfloat r)
{
  int numFaces = 2 * m*(p - 1);         // Number of faces
  int numVertices = (m + 1)*(p + 1);  // Number of vertices

  vertices.resize(numVertices);
  indices.resize(numFaces * 3);

  int verticesIndex = 0;
  int indicesIndex = 0;

  /* northern polar cap*/
  for (int j = 0; j <= m; j++)
  {
    vertices[verticesIndex] = { { 0.0f, 0.0f, r } };
    verticesIndex ++;
  }

  for (int i = 1; i < p; i++)
  {
    for (int j = 0; j <= m; j++)
    {
      float pCos = (float)cos(glm::radians(180.0f * i / p));
      float pSin = (float)sin(glm::radians(180.0f * i / p));
      float mCos = (float)cos(glm::radians(360.0f * j / m));
      float mSin = (float)sin(glm::radians(360.0f * j / m));

      vertices[verticesIndex] = { {pSin * mCos*r, pSin * mSin*r, pCos * r} };
      verticesIndex ++;
    }
  }

  /* southern polar cap*/
  for (int j = 0; j <= m; j++)
  {
    vertices[verticesIndex] = { { 0.0f, 0.0f, -r} };
    verticesIndex ++;
  }

  /* northern polar cap*/
  for (int j = 0; j < m; j++)
  {
    indices[indicesIndex] = j;
    indices[indicesIndex + 1] = m + j + 1;
    indices[indicesIndex + 2] = m + j + 2;
    indicesIndex += 3;
  }
  for (int i = 1; i < p - 1; i++)
  {
    for (int j = 0; j < m; j++)
    {
      indices[indicesIndex] = i * (m + 1) + j;
      indices[indicesIndex + 1] = (i + 1)*(m + 1) + j;
      indices[indicesIndex + 2] = i * (m + 1) + j + 1;
      indices[indicesIndex + 3] = (i + 1)*(m + 1) + j;
      indices[indicesIndex + 4] = (i + 1)*(m + 1) + j + 1;
      indices[indicesIndex + 5] = i * (m + 1) + j + 1;
      indicesIndex += 6;
    }
  }
  for (int j = 0; j < m; j++)
  {
    indices[indicesIndex] = (p - 1)*(m + 1) + j;
    indices[indicesIndex + 1] = p * (m + 1) + j;
    indices[indicesIndex + 2] = (p - 1)*(m + 1) + j + 1;
    indicesIndex += 3;
  }
}

El aspecto de la figura es el siguiente.

Captura

 

 

La clase GEDisk

 

La clase GEDisk describe un disco plano con un radio exterior y un radio interior que puede ser cero. El argumento p indica el número de sectores. El argumento m es el número de puntos de cada circunferencia. El argumento r0 es el radio interior. El argumento r1 es el radio exterior.

#pragma once

#include "GEFigure.h"

class GEDisk : public GEFigure
{
public:
  GEDisk(int p, int m, float r0, float r1);
};

El código de la clase es el siguiente.

#include "GEDisk.h"

GEDisk::GEDisk(GLint p, GLint m, GLfloat r0, GLfloat r1)
{
  if (r0 == 0.0f)
  {
    int numFaces = 2 * (2 * m * p - m);     // Number of faces
    int numVertices = 2 * (m* p + 1);       // Number of vertices
    vertices.resize(numVertices);
    indices.resize(numFaces * 3);

    int verticesIndex = 0;
    int indicesIndex = 0;

    vertices[0] = {{ 0.0f, 0.0f, 0.0f }	};
    verticesIndex ++;

    for (int j = 0; j < m; j++)
    {
      float r = (float)(r1 / p);

      float mCos = (float)cos(glm::radians(360.0f * j / m));
      float mSin = (float)sin(glm::radians(360.0f * j / m));
      vertices[verticesIndex] = { { mCos * r, mSin * r, 0.0f} };
      verticesIndex ++;

      indices[indicesIndex] = 0; // center
      indices[indicesIndex + 1] = j + 1;
      indices[indicesIndex + 2] = (j + 2 > m ? 1 : j + 2);
      indicesIndex += 3;
    }

    for (int i = 2; i <= p; i++)
    {
      for (int j = 0; j < m; j++)
      {
        float r = (float)(r1*i / p);
        float mCos = (float)cos(glm::radians(360.0f * j / m));
        float mSin = (float)sin(glm::radians(360.0f * j / m));
        vertices[verticesIndex] = { { mCos * r, mSin * r, 0.0f} };
        verticesIndex ++;
      }
    }

    for (int i = 0; i < p - 1; i++)
    {
      for (int j = 0; j < m; j++)
      {
        indices[indicesIndex] = m * i + j + 1;
        indices[indicesIndex + 1] = m * (i + 1) + j + 1;
        indices[indicesIndex + 2] = (j+1 == m? m*(i + 1) + 1 : m*(i + 1) + j + 2);
        indicesIndex += 3;

        indices[indicesIndex] = m * i + j + 1;
        indices[indicesIndex + 1] = (j+1 == m? m*(i + 1) + 1 : m*(i + 1) + j + 2);
        indices[indicesIndex + 2] = (j+1 == m? m*i + 1 : m*i + j + 2);
        indicesIndex += 3;
      }
    }

    int base = verticesIndex;

    vertices[base + 0] = { { 0.0f, 0.0f, 0.0} };
    verticesIndex ++;

    for (int j = 0; j < m; j++)
    {
      float r = (float)(r1 / p);

      float mCos = (float)cos(glm::radians(360.0f * j / m));
      float mSin = (float)sin(glm::radians(360.0f * j / m));
      vertices[verticesIndex] = { { mCos * r, mSin * r, 0.0f } };
      verticesIndex ++;

      indices[indicesIndex] = base + 0; // center
      indices[indicesIndex + 1] = base + (j + 2 > m ? 1 : j + 2);
      indices[indicesIndex + 2] = base + j + 1;
      indicesIndex += 3;
    }

    for (int i = 2; i <= p; i++)
    {
      for (int j = 0; j < m; j++)
      {
        float r = (float)(r1*i / p);
        float mCos = (float)cos(glm::radians(360.0f * j / m));
        float mSin = (float)sin(glm::radians(360.0f * j / m));
        vertices[verticesIndex] = { { mCos * r, mSin * r, 0.0f} };
        verticesIndex ++;
      }
    }

    for (int i = 0; i < p - 1; i++)
    {
      for (int j = 0; j < m; j++)
      {
        indices[indicesIndex] = base + m * i + j + 1;
        indices[indicesIndex + 1] = base + (j+1 == m? m*(i+1) + 1 : m*(i+1) + j + 2);
        indices[indicesIndex + 2] = base + m*(i + 1) + j + 1;
        indicesIndex += 3;

        indices[indicesIndex] = base + m * i + j + 1;
        indices[indicesIndex + 1] = base + (j+1 == m? m*i + 1 : m*i + j + 2);
        indices[indicesIndex + 2] = base + (j+1 == m? m*(i+1)+ 1 : m*(i+1) + j + 2);
        indicesIndex += 3;
      }
    }
  }
  else
  {
    int numFaces = 2 * (2 * m * p);      // Number of faces
    int numVertices = 2 * (m* (p + 1));  // Number of vertices
    vertices.resize(numVertices);
    indices.resize(numFaces * 3);

    int verticesIndex = 0;
    int indicesIndex = 0;

    for (int i = 0; i <= p; i++)
    {
      float r = r0 + (r1 - r0)*i / p;
      for (int j = 0; j < m; j++)
      {
        float mCos = (float)cos(glm::radians(360.0f * j / m));
        float mSin = (float)sin(glm::radians(360.0f * j / m));
        vertices[verticesIndex] = { {mCos * r, mSin * r, 0.0f } };
        verticesIndex ++;
      }
    }

    for (int i = 0; i < p; i++)
    {
      for (int j = 0; j < m; j++)
      {
        indices[indicesIndex] = m * i + j;
        indices[indicesIndex + 1] = m * (i + 1) + j;
        indices[indicesIndex + 2] = (j+1 == m ? m * (i + 1) : m * (i + 1) + j + 1);
        indicesIndex += 3;

        indices[indicesIndex] = m * i + j;
        indices[indicesIndex + 1] = (j+1 == m ? m * (i + 1) : m * (i + 1) + j + 1);
        indices[indicesIndex + 2] = (j+1 == m ? m * i : m * i + j + 1);
        indicesIndex += 3;
      }
    }

    int base = verticesIndex;

    for (int i = 0; i <= p; i++)
    {
      float r = r0 + (r1 - r0)*i / p;
      for (int j = 0; j < m; j++)
      {
        float mCos = (float)cos(glm::radians(360.0f * j / m));
        float mSin = (float)sin(glm::radians(360.0f * j / m));
        vertices[verticesIndex] = { { mCos * r, mSin * r, 0.0f} };
        verticesIndex ++;
      }
    }

    for (int i = 0; i < p; i++)
    {
      for (int j = 0; j < m; j++)
      {
        indices[indicesIndex] = base + m * i + j;
        indices[indicesIndex + 1] = base + (j+1 == m? m*(i+1) : m*(i+1) + j + 1);
        indices[indicesIndex + 2] = base + m * (i + 1) + j;
        indicesIndex += 3;

        indices[indicesIndex] = base + m * i + j;
        indices[indicesIndex + 1] = base + (j+1 == m? m*i : m*i + j + 1);
        indices[indicesIndex + 2] = base + (j+1 == m? m*(i+1) : m*(i+1) + j + 1);
        indicesIndex += 3;
      }
    }
  }
}

El aspecto de la figura es el siguiente.

Captura

 

 

La clase CATorus

 

La clase CATorus define la figura geométrica de un toride. Los argumentos p y m indican el número de puntos utilizados en cada revolución. Los argumentos r0 y r1 indican los radios de revolución.

#pragma once
#include "CAFigure.h"

class CATorus : public CAFigure
{
public:
  CATorus(int p, int m, float r0, float r1);
};

El código de la clase es el siguiente.

#include "CATorus.h"

CATorus::CATorus(GLint p, GLint m, GLfloat r0, GLfloat r1)
{
  int numFaces = 2 * m * p; // Number of faces
  int numVertices = (m + 1)*(p + 1); // Number of vertices
  vertices.resize(numVertices);
  indices.resize(numFaces * 3);

  int verticesIndex = 0;
  int indicesIndex = 0;

  for (int i = 0; i <= m; i++)
  {
    for (int j = 0; j <= p; j++)
    {
      float pCos = (float)cos(glm::radians(360.0f * j / p));
      float pSin = (float)sin(glm::radians(360.0f * j / p));
      float mCos = (float)cos(glm::radians(360.0f * i / m));
      float mSin = (float)sin(glm::radians(360.0f * i / m));

      vertices[verticesIndex] = { { (r1 + r0 * pCos)*mCos, 
                                   (r1 + r0 * pCos)*mSin, r0 * pSin} };
      verticesIndex ++;
    }
  }

  for (int i = 0; i < m; i++)
  {
    for (int j = 0; j < p; j++)
    {
      indices[indicesIndex] = (p + 1)*i + j;
      indices[indicesIndex + 1] = (p + 1)*(i + 1) + j;
      indices[indicesIndex + 2] = (p + 1)*(i + 1) + j + 1;
      indicesIndex += 3;

      indices[indicesIndex] = (p + 1)*i + j;
      indices[indicesIndex + 1] = (p + 1)*(i + 1) + j + 1;
      indices[indicesIndex + 2] = (p + 1)*i + j + 1;
      indicesIndex += 3;
    }
  }
}

El aspecto de la figura es el siguiente.

Captura

 

 

La clase CAIcosahedron

 

La clase CAIcosahedron define un icosahedro. El argumento r contiene la longitud de un lado del icosahedro.

#pragma once

#include "CAFigure.h"

class CAIcosahedron : public CAFigure
{
public:
  CAIcosahedron(float r);
};

El código de la clase es el siguiente.

#include "CAIcosahedron.h"


CAIcosahedron::CAIcosahedron(float r)
{
  int numFaces = 20; // Number of faces
  int numVertices = 60; // Number of vertices
  vertices.resize(numVertices);
  indices.resize(numFaces * 3);

  float phi = (float)((1 + sqrt(5.0)) / 2.0);

  CAVertex A0 = { { 0.0f, r*phi, r } };
  CAVertex A1 = { { 0, r*phi, -r } };
  CAVertex A2 = { { 0, -r * phi, -r } };
  CAVertex A3 = { { 0, -r * phi, r } };
  CAVertex B0 = { { r*phi, r, 0 } };
  CAVertex B1 = { { r*phi, -r, 0 } };
  CAVertex B2 = { { -r * phi, -r, 0 } };
  CAVertex B3 = { { -r * phi, r, 0 } };
  CAVertex C0 = { { r, 0, r*phi } };
  CAVertex C1 = { { r, 0, -r * phi } };
  CAVertex C2 = { { -r, 0, -r * phi } };
  CAVertex C3 = { { -r, 0, r*phi } };

  // face 1
  vertices[0] = A0;
  vertices[1] = A1;
  vertices[2] = B3;

  // face 2
  vertices[3] = A0;
  vertices[4] = B3;
  vertices[5] = C3;

  // face 3
  vertices[6] = A0;
  vertices[7] = C3;
  vertices[8] = C0;

  // face 4
  vertices[9] = A0;
  vertices[10] = C0;
  vertices[11] = B0;

  // face 5
  vertices[12] = A0;
  vertices[13] = B0;
  vertices[14] = A1;

  // face 6
  vertices[15] = B1;
  vertices[16] = C1;
  vertices[17] = B0;

  // face 7
  vertices[18] = B1;
  vertices[19] = B0;
  vertices[20] = C0;

  // face 8
  vertices[21] = B1;
  vertices[22] = C0;
  vertices[23] = A3;

  // face 9
  vertices[24] = B1;
  vertices[25] = A3;
  vertices[26] = A2;

  // face 10
  vertices[27] = B1;
  vertices[28] = A2;
  vertices[29] = C1;

  // face 11
  vertices[30] = B0;
  vertices[31] = C1;
  vertices[32] = A1;

  // face 12
  vertices[33] = C0;
  vertices[34] = C3;
  vertices[35] = A3;

  // face 13
  vertices[36] = A1;
  vertices[37] = C1;
  vertices[38] = C2;

  // face 14
  vertices[39] = A1;
  vertices[40] = C2;
  vertices[41] = B3;

  // face 15
  vertices[42] = C3;
  vertices[43] = B3;
  vertices[44] = B2;

  // face 16
  vertices[45] = C3;
  vertices[46] = B2;
  vertices[47] = A3;

  // face 17
  vertices[48] = B3;
  vertices[49] = C2;
  vertices[50] = B2;

  // face 18
  vertices[51] = C2;
  vertices[52] = C1;
  vertices[53] = A2;

  // face 19
  vertices[54] = C2;
  vertices[55] = A2;
  vertices[56] = B2;

  // face 20
  vertices[57] = A2;
  vertices[58] = A3;
  vertices[59] = B2;

  for (int i = 0; i < numVertices; i++) indices[i] = i;
}

El aspecto de la figura es el siguiente.

Captura