Escuela Técnica Superior de Ingeniería

 

Grado en Ingeniería Informática

Realidad Virtual

Curso 2023/2024

 

Práctica 4

Transformaciones geométricas

 

Objetivos

 

En esta práctica se explica como utilizar matrices de transformación para trabajar con varios sistemas de coordenadas. Esto permite definir los objetos en su propio sistema de coordenadas (local) y situarlos en una escena definida con un sistema de coordenadas del modelo (model). Además podemos mover el punto de vista del observador por medio de una objeto cámara que utiliza su propio sistema de coordenadas (view). A esto hay que añadir la transformación entre este sistema de cordenadas y el sistema de cordenadas de Clip (lo que se conoce como proyección).

 

 

Código de la práctica

 

 

La clase CGFigure

 

Para considerar las transformaciones que permitan situar figuras en diferentes posiciones dentro de una escena es necesario incuir algunos cambios a la clase CGFigure que se introdujo en la práctica anterior. Estos cambios se centran en incluir un campo location que almacena la matriz de transformación entre el sistema de cordenadas de la figura (local) y el sistema de coordenadas del modelo (model). Para facilitar la colocación de las figuras en la escena se van a añadir también algunos métodos que permiten modificar la situación del objeto por medio de traslaciones y rotaciones. El fichero de cabecera de esta clase quedaría así:

#pragma once

#include <GL/glew.h>
#include <glm/glm.hpp>
#include "CGShaderProgram.h"

#define VERTEX_DATA     0
#define INDEX_DATA      1

//
// CLASE: CGFigure
//
// DESCRIPCIÓN: Clase abstracta que representa un objeto descrito mediante
//              VAO para su renderizado mediante shaders
// 
class CGFigure {
protected:
  GLushort* indexes;  // Array of indexes 
  GLfloat* vertices;  // Array of vertices

  int numFaces;     // Number of faces
  int numVertices;  // Number of vertices
  GLuint VBO[2];
  GLuint VAO;

  glm::mat4 location; // Model matrix

public:
  ~CGFigure();
  void InitBuffers();
  void ResetLocation();
  void Translate(glm::vec3 t);
  void Rotate(GLfloat angle, glm::vec3 axis);
  void Draw(CGShaderProgram* program, glm::mat4 projection, glm::mat4 view);
};

La función ResetLocation() asigna la matriz unidad al campo location. Esto indica que el sistema de coordenadas de la figura coincide con el sistema de coordenadas de la escena, es decir, que la posición de la figura se encuentra en el origen de coordenadas de la escena y que su orientación corresponde a los ejes del sistema de coordenadas de la escena. La función Translate() añade una traslación a la matriz de transformación. Matemáticamente esto significa multiplicar la matriz location por la derecha por una matriz de desplazamiento. El resultado es que la figura se dibujará desplazada sobre el sistema de coordenadas de la escena. La función Rotate() añade una rotación a la matriz de transformación. El resultado es que la figura se dibujará girada con respecto al sistema de coordenadas de la escena. Matemáticamente esta función multiplica la matriz location por la derecha por una matriz de rotación. La función Draw() se ha modificado para recibir como atributos la matriz view (que transforma las coordenadas de la escena en coordenadas del observador) y la matriz projection (que transforma las coordenadas del observador en coordenadas clip). La transformación a desarrollar en el VertexShader es la composición de las tres transformaciones (model-view-projection). El contenido del fichero de código de la clase CGFigure es el siguiente.

#include "CGFigure.h"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

CGFigure::~CGFigure()
{
  if (vertices != NULL) delete[] vertices;
  if (indexes != NULL) delete[] indexes;

  // Delete vertex buffer objects
  glDeleteBuffers(2, VBO);
  
  // Delete vertex array object
  glDeleteVertexArrays(1, &VAO);
}

void CGFigure::InitBuffers()
{
  // Create the Vertex Array Object
  glGenVertexArrays(1, &VAO);
  glBindVertexArray(VAO);

  // Create the Vertex Buffer Objects
  glGenBuffers(2, VBO);

  // Copy data to video memory
  // Vertex data
  int buffersize = sizeof(GLfloat) * numVertices * 3;
  glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
  glBufferData(GL_ARRAY_BUFFER, buffersize, vertices, GL_STATIC_DRAW);

  // Indexes
  buffersize = sizeof(GLushort) * numFaces * 3;
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO[INDEX_DATA]);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffersize, indexes, GL_STATIC_DRAW);

  delete[] vertices;
  delete[] indexes;

  vertices = NULL;
  indexes = NULL;

  glEnableVertexAttribArray(0); // Vertex position
  glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
  
  location = glm::mat4(1.0f);
}

void CGFigure::ResetLocation()
{
  location = glm::mat4(1.0f);
}

void CGFigure::Translate(glm::vec3 t)
{
  location = glm::translate(location, t);
}

void CGFigure::Rotate(GLfloat angle, glm::vec3 axis)
{
  location = glm::rotate(location, glm::radians(angle), axis);
}

void CGFigure::Draw(CGShaderProgram* program, glm::mat4 projection, glm::mat4 view)
{
  glm::mat4 mvp = projection * view * location;
  program->SetUniformMatrix4("MVP", mvp);

  glBindVertexArray(VAO);
  glDrawElements(GL_TRIANGLES, numFaces * 3, GL_UNSIGNED_SHORT, NULL);
}

 

 

La figura CGGround

 

Las figuras que vamos a situar en la escena son las mismas que las definidas en la práctica anterior (cubo, pirámide, cono, cilindro, esfera, disco, toro e icosahedro). Como los cambios planteados son comunes a todas las figuras y se han incorporado a la clase CGFigure, no es necesario plantear modificaciones en el código de estas clases. Para dibujar la escena se va a añadir un suelo formado por una superficie cuadrada. Este suelo requiere una nueva clase, que vamos a denominar CGGround, que tenemos que añadir. A continuación se muestra el fichero de cabecera asociado a esta nueva figura.

#pragma once

#include <GL/glew.h>
#include "CGFigure.h"

//
// CLASE: CGGround
//
// DESCRIPCIÓN: Representa un cuadrado dibujado en el suelo
// 
class CGGround : public CGFigure {
public:
  CGGround(GLfloat l1, GLfloat l2);
};

El constructor de la clase se describe en el siguiente fichero de código.

#include "CGGround.h"
#include <GL/glew.h>
#include "CGFigure.h"

CGGround::CGGround(GLfloat l1, GLfloat l2)
{
  numFaces = 2; // Number of faces
  numVertices = 4; // Number of vertices

  GLfloat p_vertices[4][3] = {
    { l1, 0.0f, l2 },
    { l1, 0.0f, -l2 },
    { -l1, 0.0f, -l2 },
    { -l1, 0.0f, l2 }
  };

  GLushort p_indexes[2][3] = {
    { 0, 1, 2 },
    { 0, 2, 3 }
  };

  vertices = new GLfloat[numVertices * 3];
  for (int i = 0; i < numVertices; i++)
    for (int j = 0; j < 3; j++)
      vertices[3 * i + j] = p_vertices[i][j];

  indexes = new GLushort[numFaces * 3];
  for (int i = 0; i < numFaces; i++)
    for (int j = 0; j < 3; j++)
       indexes[3 * i + j] = p_indexes[i][j];

  InitBuffers();
}

 

 

El programa gráfico

 

El programa gráfico que vamos a utilizar es el mismo que el planteado en la práctica anterior. El VertexShader incluye tan solo una única matriz de transformación llamada MVP, que debe contener la composición de transformaciones model-view-projection.

#version 400

in vec3 VertexPosition;

uniform mat4 MVP;

void main()
{
    gl_Position = MVP * vec4(VertexPosition, 1.0);
}

Por su parte, el FragmentShader sigue siendo el mismo y se limita a dibujar de rojo los píxeles de cada fragmento.

#version 400

out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0,0.0,0.0,1.0);
} 

 

 

Descripción de la escena (clase CGScene)

 

La escena que queremos mostrar está formada por seis figuras geométricas situadas sobre un plano. El plano es un cuadrado con un lado de tamaño 100 generado por una figura CGGround. El sistema de coordenadas del modelo tiene su origen en el centro del cuadrado, siendo los ejes X y Z los que marcan la superficie del cuadrado y el eje Y la vertical. Las figuras incluidas en la escena son un cubo, un cono, un cilindro, un toro, una esfera y un icosahedro. Estas figuras se van a colocar formando dos hileras a una distancia de 25 sobre el eje X respecto al centro de la escena. Las figuras se van a rotar y desplazar verticalmente para que aparezcan apoyadas sobre el suelo.

El fichero CGScene.h contiene la descripción de la clase CGScene que describe la escena completa. La interfaz pública de la clase contiene el constructor de la clase, su destructor y el método Draw() que debe generar la escena. Internamente la clase contiene siete objetos CGFigure, que almacenarán las figuras geométricas y el suelo.

#pragma once

#include <GL/glew.h>
#include <glm/glm.hpp>
#include "CGShaderProgram.h"
#include "CGFigure.h"

class CGScene {
public:
  CGScene();
  ~CGScene();
  void Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view);

private:
  CGFigure* ground;
  CGFigure* fig0;
  CGFigure* fig1;
  CGFigure* fig2;
  CGFigure* fig3;
  CGFigure* fig4;
  CGFigure* fig5;
};

El fichero CGScene.cpp contiene el desarrollo de la clase CGScene que describe la escena. El constructor de la clase es el encargado de crear los objetos CGFigure incluidos en la escena y situarlos mediante llamadas a los métodos Translate() y Rotate(). El destructor de la clase se limita a destruir los objetos que contienen las figuras. El método Draw() es el encargado de generar la imagen y se limita a ejecutar los métodos Draw() de las figuras que la forman.

#include "CGScene.h"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "CGShaderProgram.h"
#include "CGFigure.h"
#include "CGCube.h"
#include "CGCone.h"
#include "CGCylinder.h"
#include "CGSphere.h"
#include "CGTorus.h"
#include "CGIcosahedron.h"
#include "CGGround.h"

//
// FUNCIÓN: CGScene::CGScene()
//
// PROPÓSITO: Construye el objeto que representa la escena
//
CGScene::CGScene()
{
  ground = new CGGround(50.0f, 50.0f);

  fig0 = new CGCone(5, 20, 5.0f, 5.0f);
  fig0->Translate(glm::vec3(25.0f, 5.0f, 25.0f));
  fig0->Rotate(-90.0f, glm::vec3(1.0f, 0.0f, 0.0f));

  fig1 = new CGCube(5.0f);
  fig1->Translate(glm::vec3(-25.0f, 5.0f, 25.0f));
  fig1->Rotate(-90.0f, glm::vec3(1.0f, 0.0f, 0.0f));

  fig2 = new CGCylinder(20, 20, 5.0f, 5.0f);
  fig2->Translate(glm::vec3(25.0f, 5.0f, 0.0f));
  fig2->Rotate(90.0f, glm::vec3(1.0f, 0.0f, 0.0f));

  fig3 = new CGTorus(20, 40, 3.0f, 5.0f);
  fig3->Translate(glm::vec3(-25.0f, 8.0f, 0.0f));

  fig4 = new CGSphere(20, 40, 8.0f);
  fig4->Translate(glm::vec3(25.0f, 8.0f, -25.0f));

  fig5 = new CGIcosahedron(5.0f);
  fig5->Translate(glm::vec3(-25.0f, 8.0f, -25.0f));
}

//
// FUNCIÓN: CGScene3:~CGScene()
//
// PROPÓSITO: Destruye el objeto que representa la escena
//
CGScene::~CGScene()
{
  delete ground;
  delete fig0;
  delete fig1;
  delete fig2;
  delete fig3;
  delete fig4;
  delete fig5;
}

//
// FUNCIÓN: CGScene::Draw()
//
// PROPÓSITO: Dibuja la escena
//
void CGScene::Draw(CGShaderProgram* program, glm::mat4 proj, glm::mat4 view)
{
  ground->Draw(program, proj, view);
  fig0->Draw(program, proj, view);
  fig1->Draw(program, proj, view);
  fig2->Draw(program, proj, view);
  fig3->Draw(program, proj, view);
  fig4->Draw(program, proj, view);
  fig5->Draw(program, proj, view);
}

La siguiente imagen muestra una visión de la escena desde el punto de vista de un observador situado en una posición centrada respecto al eje X y algo elevada y alejada para obtener una buena perspectiva.

Escena

 

 

 

Descripción de la cámara (clase CGCamera)

 

Para poder desplazarse a través de la escena es necesario establecer la posición y orientación del observador y realizar una transformación del sistema de coordenadas entre el sistema de coordenadas del modelo y el sistema de coordenadas del observador.

Sistemas de Coordenadas

La posición del observador, en coordenadas del modelo, viene dada por el vector pos. Los ejes de coordenadas del observador, en coordenadas del modelo, vienen dados por los vectores right, up y dir. Para transformar las coordenadas del modelo en coordenadas del observador el primer paso es realizar una traslación de (-pos). De esta forma, el origen de coordenadas del modelo pasa a estar situado en la posición (-pos) y el origen de coordenadas del observador pasa a estar situado en la posición (0,0,0).

Traslación

Para completar la transformación del sistema de coordenadas es necesario rotar los ejes. El resultado de esta rotación es que el vector right debe convertirse en el vector unitario del eje X en el sistema de coordenadas del observador, es decir, en el vector (1,0,0); el vector up debe transformarse en el vector unitario sobre el eje Y, es decir, en el vector (0,1,0); y el vector dir debe convertirse en el vector unitario sobre el eje Z, es decir, el vector (0,0,1). A continuación se muestra el contenido de la matriz de rotación.

Rotación

La matriz de transformación completa se obtiene multiplicando la matriz de rotación y la matriz de traslación. Esta matriz de transformación se puede generar facilmente utilizando la función glm::lookAt(eye,center,up) incluida en la biblioteca GLM. El primer argumento es la posición del observador. El segundo argumento es la posición de un punto situado sobre el eje dir. El tercer argumento de esta función es el vector up.

Para controlar las propiedades del observador vamos a definir una nueva clase que denominamos CGCamera. Esta clase nos permitirá almacenar la posición y orientación del observador y moverlo libremente a través de la escena. La descripción de esta clase se encuentra en el fichero CGCamera.h. Los miembros de la clase, descritos en su zona privada, almacenan los valores de los vectores pos, right, up y dir. También se incluyen los campos moveStep y turnStep que contienen la magnitud de los desplazamientos y giros a realizar en cada paso de la animación. Los campos cosAngle y sinAngle almacenan el seno y coseno de turnStep para que no sea necesario recalcularlos constantemente. La interfaz pública de la clase CGCamera consta del constructor, setters y getters parta los campos, métodos Move..() para realizar desplazamientos sobre los ejes de coordenadas, métodos Turn...() para realizar rotaciones sobre los ejes de coordenadas y el método ViewMatrix() que devuelve la matriz de transformación entre el sistema de coordenadas del modelo y el sistema de coordenadas del observador.

#pragma once

#include <GL/glew.h>
#include <glm/glm.hpp>

class CGCamera {
public:
  CGCamera();
  glm::mat4 ViewMatrix();

  void SetPosition(GLfloat x, GLfloat y, GLfloat z);
  void SetDirection(GLfloat xDir, GLfloat yDir, GLfloat zDir, 
                    GLfloat xUp, GLfloat yUp, GLfloat zUp);
  void SetMoveStep(GLfloat step);
  void SetTurnStep(GLfloat step);

  glm::vec3 GetPosition();
  glm::vec3 GetDirection();
  glm::vec3 GetUpDirection();
  GLfloat GetMoveStep();
  GLfloat GetTurnStep();

  void MoveFront();
  void MoveBack();
  void MoveRight();
  void MoveLeft();
  void MoveUp();
  void MoveDown();

  void TurnRight();
  void TurnLeft();
  void TurnUp();
  void TurnDown();
  void TurnCW();
  void TurnCCW();

private:
  glm::vec3 Pos;
  glm::vec3 Dir;
  glm::vec3 Up;
  glm::vec3 Right;

  GLfloat moveStep;
  GLfloat turnStep;
  GLfloat cosAngle;
  GLfloat sinAngle;
};

El desarrollo de la clase CGCamera se encuentra en el archivo CGCamera.cpp, que se muestra a continuación. El constructor de la clase inicializa la posición y orientación del observador de manera que coincide con el sistema de coordenadas del modelo. El método ViewMatrix()  desarrolla la transformación de los sistemas de coordenadas utilizando la función lookAt() de la biblioteca GLM. Los métodos Get...() se limitan a obtener los valores de los campos internos de la clase. Con respecto a los métodos Set...() es importante señalar que la orientación se fija en un único método que solo asigna los valores de dir y up. Se asume que estos vectores son unitarios y perpendiculares entre sí. El vector right se calcula como el producto vectorial entre up y dir. Los métodos Move...() generan traslaciones de módulo moveStep sobre cada uno de los ejes del sistema de coordenadas del observador. Esto supone modificar el vector pos añadiendo un desplazamiento de moveStep por el vector unitario de la traslación. Por ejemplo, MoveRight() genera un desplazamiento en la dirección del eje X (vector right) y MoveFront() genera un desplazamiento hacia adelante (dirección -Z, es decir, -dir). Los métodos Turn...() realizan giros de magnitud turnStep sobre cada uno de los ejes del sistema de coordenadas. Para evitar que por problemas de redondeo se pierda la ortogonalidad de los vectores right, up y dir, los giros se calculan sobre un único vector y el otro vector afectado se recalcula mediante un producto vectorial. Los métodos TurnLeft() y TurnRight() realizan giros respecto al eje Y. Los métodos TurnUp() y TurnDown() realizan giros respecto al eje X. Los métodos TurnCW() y TurnCCW() realizan giros respecto al eje Z.

#include "CGCamera.h"
#include <GL/glew.h>
#include <math.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

//
// FUNCIÓN: CGCamera::CGCamera()
//
// PROPÓSITO: Construye una cámara
//
// COMENTARIOS: 
//     La posición inicial es (0,0,0).
//     La orientación incial es el sistema de coordenadas del modelo
//     El tamaño del paso inicial es 0.1
//     El tamaño del giro inicial es 1.0 grados
//
CGCamera::CGCamera()
{
  Pos = glm::vec3(0.0f, 0.0f, 0.0f);
  Dir = glm::vec3(0.0f, 0.0f, 1.0f);
  Up = glm::vec3(0.0f, 1.0f, 0.0f);
  Right = glm::vec3(1.0f, 0.0f, 0.0f);

  moveStep = 0.1f;
  turnStep = 1.0f;
  cosAngle = (GLfloat)cos(glm::radians(turnStep));
  sinAngle = (GLfloat)sin(glm::radians(turnStep));
}

//
// FUNCIÓN: CGCamera::ViewMatrix()
//
// PROPÓSITO: Obtiene la matriz View para situar la cámara.
//
glm::mat4 CGCamera::ViewMatrix()
{
  return glm::lookAt(Pos, Pos - Dir, Up);
}

//
// FUNCIÓN: CGCamera::SetPosition(GLfloat x, GLfloat y, GLfloat z)
//
// PROPÓSITO: Asigna la posición de la cámara con respecto al 
//            sistema de coordenadas del modelo.
//
void CGCamera::SetPosition(GLfloat x, GLfloat y, GLfloat z)
{
  Pos = glm::vec3(x, y, z);
}

//
// FUNCIÓN: CGCamera::SetDirection(GLfloat xDir, GLfloat yDir, GLfloat zDir, 
//                                 GLfloat xUp, GLfloat yUp, GLfloat zUp)
//
// PROPÓSITO: Asigna la orientación de la cámara.
//
void CGCamera::SetDirection(GLfloat xD, GLfloat yD, GLfloat zD, 
                            GLfloat xU, GLfloat yU, GLfloat zU)
{
  Dir = glm::vec3(xD, yD, zD);
  Up = glm::vec3(xU, yU, zU);
  Right = glm::cross(Up, Dir);
}

//
// FUNCIÓN: CGCamera::SetMoveStep(GLfloat step)
//
// PROPÓSITO: Asigna el avance en cada paso.
//
void CGCamera::SetMoveStep(GLfloat step)
{
  moveStep = step;
}

//
// FUNCIÓN: CGCamera::SetTurnStep(GLfloat step)
//
// PROPÓSITO: Asigna el ángulo de giro en cada paso.
//
void CGCamera::SetTurnStep(GLfloat step)
{
  turnStep = step;
  cosAngle = (GLfloat)cos(glm::radians(turnStep));
  sinAngle = (GLfloat)sin(glm::radians(turnStep));
}

//
// FUNCIÓN: CGCamera::GetPosition()
//
// PROPÓSITO: Obtiene la posición de la cámara.
//
glm::vec3 CGCamera::GetPosition()
{
  return Pos;
}

//
// FUNCIÓN: CGCamera::GetDirection()
//
// PROPÓSITO: Obtiene la orientación de la cámara (eje Z).
//
glm::vec3 CGCamera::GetDirection()
{
  return Dir;
}

//
// FUNCIÓN: CGCamera::GetUpDirection()
//
// PROPÓSITO: Obtiene la orientación cenital de la cámara (eje Y).
//
glm::vec3 CGCamera::GetUpDirection()
{
  return Up;
}

//
// FUNCIÓN: CGCamera::GetMoveStep()
//
// PROPÓSITO: Obtiene el avance en cada paso.
//
GLfloat CGCamera::GetMoveStep()
{
  return moveStep;
}

//
// FUNCIÓN: CGCamera::GetTurnStep()
//
// PROPÓSITO: Obtiene el ángulo de giro en cada paso.
//
GLfloat CGCamera::GetTurnStep()
{
  return turnStep;
}

//
// FUNCIÓN: CGCamera::MoveFront()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) en la dirección -Dir 
//
void CGCamera::MoveFront()
{
  Pos -= moveStep * Dir;
}

//
// FUNCIÓN: CGCamera::MoveBack()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia atrás en la dirección Dir 
//
void CGCamera::MoveBack()
{
  Pos += moveStep * Dir;
}

//
// FUNCIÓN: CGCamera::MoveLeft()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia la izquierda. 
//
void CGCamera::MoveLeft()
{
  Pos -= moveStep * Right;
}

//
// FUNCIÓN: CGCamera::MoveRight()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia la derecha. 
//
void CGCamera::MoveRight()
{
  Pos += moveStep * Right;
}

//
// FUNCIÓN: CGCamera::MoveUp()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia arriba. 
//
void CGCamera::MoveUp()
{
  Pos += moveStep * Up;
}

//
// FUNCIÓN: CGCamera::MoveDown()
//
// PROPÓSITO: Mueve el observador un paso (moveStep) hacia abajo. 
//
void CGCamera::MoveDown()
{
  Pos -= moveStep * Up;
}

//
// FUNCIÓN: CGCamera::TurnRight()
//
// PROPÓSITO: Rota el observador un paso (angleStep) hacia su derecha.
//
void CGCamera::TurnRight()
{
  Dir.x = cosAngle * Dir.x - sinAngle * Right.x;
  Dir.y = cosAngle * Dir.y - sinAngle * Right.y;
  Dir.z = cosAngle * Dir.z - sinAngle * Right.z;

  // Right = Up x Dir
  Right = glm::cross(Up, Dir);
}

//
// FUNCIÓN: CGCamera::TurnLeft()
//
// PROPÓSITO: Rota el observador un paso (angleStep) hacia su izquierda.
//
void CGCamera::TurnLeft()
{
  Dir.x = cosAngle * Dir.x + sinAngle * Right.x;
  Dir.y = cosAngle * Dir.y + sinAngle * Right.y;
  Dir.z = cosAngle * Dir.z + sinAngle * Right.z;

  // Right = Up x Dir
  Right = glm::cross(Up, Dir);
}

//
// FUNCIÓN: CGCamera::TurnUp()
//
// PROPÓSITO: Rota el observador un paso (angleStep) hacia arriba.
//
void CGCamera::TurnUp()
{
  Dir.x = cosAngle * Dir.x - sinAngle * Up.x;
  Dir.y = cosAngle * Dir.y - sinAngle * Up.y;
  Dir.z = cosAngle * Dir.z - sinAngle * Up.z;

  // Up = Dir x Right
  Up = glm::cross(Dir, Right);
}

//
// FUNCIÓN: CGCamera::TurnDown()
//
// PROPÓSITO: Rota el observador un paso (angleStep) hacia abajo.
//
void CGCamera::TurnDown()
{
  Dir.x = cosAngle * Dir.x + sinAngle * Up.x;
  Dir.y = cosAngle * Dir.y + sinAngle * Up.y;
  Dir.z = cosAngle * Dir.z + sinAngle * Up.z;

  // Up = Dir x Right
  Up = glm::cross(Dir, Right);
}

//
// FUNCIÓN: CGCamera::TurnCW()
//
// PROPÓSITO: Rota el observador un paso (angleStep) en sentido del reloj.
//
void CGCamera::TurnCW()
{
  Up.x = cosAngle * Up.x + sinAngle * Right.x;
  Up.y = cosAngle * Up.y + sinAngle * Right.y;
  Up.z = cosAngle * Up.z + sinAngle * Right.z;

  // Right = Up x Dir
  Right = glm::cross(Up, Dir);
}

//
// FUNCIÓN: CGCamera::TurnDown()
//
// PROPÓSITO: Rota el observador un paso (angleStep) en sentido contrario al reloj.
//
void CGCamera::TurnCCW()
{
  Up.x = cosAngle * Up.x - sinAngle * Right.x;
  Up.y = cosAngle * Up.y - sinAngle * Right.y;
  Up.z = cosAngle * Up.z - sinAngle * Right.z;

  // Right = Up x Dir
  Right = glm::cross(Up, Dir);
}

 

Descripción del modelo 3D (clase CGModel)

 

El modelo a representar en nuestra aplicación se desarrolla por medio de la clase CGModel. Esta clase se describe en el fichero CGModel.h mostrado a continuación. En este caso, el modelo está formado simplemente por el programa gráfico, la cámara y la escena. También se almacena como campo la matriz de proyección. La interfaz pública de la clase es la que se utiliza en todas las prácticas.

#pragma once

#include <GL/glew.h>
#include "CGShaderProgram.h"
#include "CGScene.h"
#include "CGCamera.h"

class CGModel
{
public:
  void initialize(int w, int h);
  void finalize();
  void render();
  void update();
  void key_pressed(int key);
  void mouse_button(int button, int action);
  void mouse_move(double xpos, double ypos);
  void resize(int w, int h);

private:
  CGShaderProgram* program;
  CGScene* scene;
  CGCamera* camera;

  glm::mat4 projection;
};

El código asociado al modelo se describe en el fichero CGModel.cpp. El método initialize() de la clase CGModel es el encargado de crear los objetos CGShaderProgram, CGCamera y CGScene. Inicialmente la cámara se coloca en una posición que permita apreciar la escena. El método finalize() se encarga de destruir los objetos program, camera y scene. El método render(), dedicado a generar la imagen, se limita a obtener la matriz de transformación de la cámara y dibujar la escena. La acción de actualización consiste en mover la cámara hacia el frente. De esta manera, si el valor de moveStep en la cámara es positivo la cámara se mueve hacia adelante de forma automática. El método key_pressed() asocia movimientos de la cámara a algunas teclas, de manera que el usuario pueda desplazar libremente la cámara a través de la escena. Las teclas Q y A mueven la cámara arriba y abajo un único paso. Las teclas O y P mueven la cámara a izquierda y derecha un único paso. Las teclas '+' y '-' permiten acelarar y frenar la cámara. Para detener la cámara se utiliza la tecla S. Los giros de cámara se realizan con las flechas y las teclas K y L, dedicadas a girar a la derecha y a la izquierda.

#include "CGModel.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <GLFW/glfw3.h>
#include <iostream>
#include "CGCamera.h"
#include "CGScene.h"


//
// FUNCIÓN: CGModel::initialize(int, int)
//
// PROPÓSITO: Initializa el modelo 3D
//
void CGModel::initialize(int w, int h)
{
  // Crea el programa
  program = new CGShaderProgram(IDR_SHADER1, IDR_SHADER2, -1, -1, -1);
  //program = new CGShaderProgram("shaders/VertexShader.glsl", 
  //                              "shaders/FragmentShader.glsl", NULL, NULL, NULL);
  if (program->IsLinked() == GL_FALSE) return;
  program->Use();

  // Crea la cámara
  camera = new CGCamera();
  camera->SetPosition(0.0f, 5.0f, 30.0f);

  // Crea la escena
  scene = new CGScene();

  // Asigna el viewport y el clipping volume
  resize(w, h);

  // Opciones de dibujo
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glFrontFace(GL_CCW);
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}

//
// FUNCIÓN: CGModel::finalize()
//
// PROPÓSITO: Libera los recursos del modelo 3D
//
void CGModel::finalize()
{
  delete camera;
  delete scene;
  delete program;
}

//
// FUNCIÓN: CGModel::resize(int w, int h)
//
// PROPÓSITO: Asigna el viewport y el clipping volume
//
void CGModel::resize(int w, int h)
{
  double fov = glm::radians(15.0);
  double sin_fov = sin(fov);
  double cos_fov = cos(fov);
  if (h == 0) h = 1;
  GLfloat aspectRatio = (GLfloat)w / (GLfloat)h;
  GLfloat wHeight = (GLfloat)(sin_fov * 0.2 / cos_fov);
  GLfloat wWidth = wHeight * aspectRatio;

  glViewport(0, 0, w, h);
  projection = glm::frustum(-wWidth, wWidth, -wHeight, wHeight, 0.2f, 400.0f);
}

//
// FUNCIÓN: CGModel::render()
//
// PROPÓSITO: Genera la imagen
//
void CGModel::render()
{
  glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glm::mat4 view = camera->ViewMatrix();
  scene->Draw(program, projection, view);
}

//
// FUNCIÓN: CGModel::update()
//
// PROPÓSITO: Anima la escena
//
void CGModel::update()
{
  camera->MoveFront();
}

//
// FUNCIÓN: CGModel::key_pressed(int)
//
// PROPÓSITO: Respuesta a acciones de teclado
//
void CGModel::key_pressed(int key)
{
  switch (key)
  {
  case GLFW_KEY_UP:
    camera->TurnDown();
    break;
  case GLFW_KEY_DOWN:
    camera->TurnUp();
    break;
  case GLFW_KEY_LEFT:
    camera->TurnCCW();
    break;
  case GLFW_KEY_RIGHT:
    camera->TurnCW();
    break;
  case GLFW_KEY_S:
    camera->SetMoveStep(0.0f);
    break;
  case GLFW_KEY_RIGHT_BRACKET:
  case GLFW_KEY_KP_ADD:
    camera->SetMoveStep(camera->GetMoveStep() + 0.1f);
    break;
  case GLFW_KEY_MINUS:
  case GLFW_KEY_KP_SUBTRACT:
    camera->SetMoveStep(camera->GetMoveStep() - 0.1f);
    break;
  case GLFW_KEY_Q:
    camera->SetMoveStep(0.1f);
    camera->MoveUp();
    camera->SetMoveStep(0.0f);
    break;
  case GLFW_KEY_A:
    camera->SetMoveStep(0.1f);
    camera->MoveDown();
    camera->SetMoveStep(0.0f);
    break;
  case GLFW_KEY_O:
    camera->SetMoveStep(0.1f);
    camera->MoveLeft();
    camera->SetMoveStep(0.0f);
    break;
  case GLFW_KEY_P:
    camera->SetMoveStep(0.1f);
    camera->MoveRight();
    camera->SetMoveStep(0.0f);
    break;
  case GLFW_KEY_K:
    camera->TurnLeft();
    break;
  case GLFW_KEY_L:
    camera->TurnRight();
    break;
  }
}

//
//  FUNCIÓN: CGModel:::mouse_button(int button, int action)
//
//  PROPÓSITO: Respuesta del modelo a un click del ratón.
//
void CGModel::mouse_button(int button, int action)
{
}

//
//  FUNCIÓN: CGModel::mouse_move(double xpos, double ypos)
//
//  PROPÓSITO: Respuesta del modelo a un movimiento del ratón.
//
void CGModel::mouse_move(double xpos, double ypos)
{
}