|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|