Computação Gráfica
Prof.
Márcio Sarroglia Pinho
Transformações
Geométricas Hierárquicas em OpenGL
|
RESUMO
O
objetivo deste trabalho é criar um Grafo de Cena.
Os
fontes para o desenvolvimento do trabalho em C++ e Python estão neste link.
No
caso de C++, no Windows e no Linux, abra o projeto OpenGL.cbp, no
Code::Blocks. No MacOS, abra o projeto OpenGL.xcodeproj, no XCode. Para
construir seu próprio projeto em C++, insira os seguintes fontes:
HierarquiaV2.cpp, Desenhos.cpp, ListaDeCoresRGB.cpp, Objeto.cpp, Ponto.cpp,
Tools.cpp.
Os
fontes para o desenvolvimento do trabalho em Python, estão neste link.
Para rodar o programa, execute o script HierarquiaV2.py.
Para
obter instruções sobre como criar um projeto OpenGL acesse a página https://tiny.cc/PinhoOpenGL.
Compile
o programa e controle os objetos na tela com as seguintes teclas:
· 0, 1, 2, ....: definem qual é objeto ativo;
· teclas de seta LEFT, RIGHT: movem o objeto ativo para
frente/trás.
· teclas de seta UP, DOWN: giram o objeto ativo para
esquerda/direita.
· barra de espaço: imprime no console as coordenadas do objeto
ativo.
Tools.cpp e Tools.h
Contém funções auxiliares de cálculos matemáticos. Analise o Tools.cpp para
ver a descrição das funções.
void
InverteMatriz( float a[4][4], float b[4][4]);
void
MultiplicaMatriz(float m1[4][4], float m2[4][4], float m[4][4]);
float
calculaDistancia(float P1[3], float P2[3]);
void
criaIdentidade(float m1[4][4]);
Desenhos.cpp e Desenhos.h
Contém as funções de desenhos dos objetos em coordenadas do sistema de
referência do objeto, ou seja, SEM as transformações geométricas que
definem a posição, a rotação e a escala de uma instância.
Normalmente, é neste fonte que devem
ser colocadas os comandos OpenGL que fazem os desenhos.
void DesenhaContornoCirculo(GLfloat
raio, GLfloat centrox, GLfloat centroy);
void DesenhaCirculo(GLfloat raio, GLfloat centrox, GLfloat
centroy);
void DesenhaTexto(char *string, GLfloat posX, GLfloat posY);
void DesenhaCarro();
void DesenhaHelicoptero();
Objeto.cpp e Objeto.h
Contém a estrutura capaz de armazenar uma instância de um objeto. Os métodos
que aplicam as transformações geométricas estão nestes arquivos.
A classe que define esta instância é a seguinte:
class Objeto {
int tipo;
int id;
int nroDeFilhos;
bool eh_filho;
Objeto *Filho;
Objeto *Pai;
GLfloat M[4][4];
public:
Objeto();
void setID(int i);
void setTipo(int t);
void Init();
void Rotate(float ang, float x, float y, float z);
void Translate(float x, float y, float z);
void Scale(float x, float y, float z);
void Draw();
void ImprimeMatriz(const char *msg);
void InstanciaPonto(float PontoIN[3], float PontoOUT[3]);
void AdicionaFilho(Objeto *filho);
void RemoveFilho();
};
Os atributos têm o seguinte significado:
int tipo; // define se o objeto é um
helicoptero(0) ou um carro(1)
int nroDeFilhos; //define quantos filhos tem o objeto no
momento
bool eh_filho; // informa se o objeto é filho de algum outro
objeto do cenário
Objeto *Filhos; // vetor com referências para os filhos do
objeto
GLfloat M[4][4]; // matriz de
transformação do objeto
Objeto *Pai; // Referência para o pai do objeto
Analise o fonte Objeto.cpp e veja a descrição de cada método.
HierarquiaV2.cpp
Este arquivo é módulo principal do programa. Ele controla a exibição do
cenário.
O programa trabalha com o conceito de objeto
ativo, que define sobre qual objeto as transformações comandadas pelo
teclado serão aplicadas. Para ativar um objeto pressione 0(helicóptero),
1(carro) ou 2(carro).
As instâncias dos objetos estão armazenadas no vetor
Objeto Cenario[10];
As principais funções do módulo são:
Função Init():
Faz as inicializações de OpenGL, definindo a cor de fundo e os limites da área
de desenho através das variáveis Min e Max.
A função também chama a função CarregaModelos que cria as
instâncias que compõem o cenário.
Função CarregaModelos ():
Define quais instâncias fazem parte do
cenário.
O
trecho de código a seguir inicializa as instâncias. No caso, há 3 instâncias. A
ZERO é um helicóptero e as demais são carros.
void CarregaModelos () {
QTD_OBJETOS = 3;
ObjetoAtivo = 0;
for(int i=1; i<QTD_OBJETOS; i++) {
Cenario[i].Translate(i*600,i*300,0);
Cenario[i].setTipo(2); // carro
Cenario[i].setID(i);
}
Cenario[0].setTipo(1); // Helicoptero
// Helicoptero.ImprimeMatriz("Apos
o INIT");
imprimeCoordenadas = false;
tentaPegar = false;
}
Função Desenha():
Realiza o desenho do cenário. Os principais passos são:
· Limpa a tecla;
· Desenha os eixos
coordenados;
· Imprime mensagens de
texto;
· Desenha o cada objeto
armazenado no vetor "Cenario", a partir do código a seguir.
// Desenha o cenario todo
for(int i=0; i<3; i++) {
Cenario[i].Draw();
}
Também se pode, nesta função, imprimir as posições de cada objeto do cenário
com o trecho apresentado a seguir. A variável imprimeCoordenadas
é que determina a
chamada desta função e pode ser alterada pela tecla espaço.
void imprimeCoordenadasDosObjetos()
{
GLfloat P1[] = {0,0,0};
GLfloat P2[3];
imprimeCoordenadas = false;
printf("Posicoes do
objetos:\n");
for(int i=0; i<QTD_OBJETOS; i++) {
printf("Objeto %d:
",i);
Cenario[i].InstanciaPonto(P1,P2);
for (int j=0; j<3; j++)
printf("%7.2f ", P2[j]);
printf("\n");
}
printf("======================================\n");
}
O
objetivo deste exercício é permitir que o helicóptero possa ‘pegar’
um carro e transportá-lo pelo cenário até um ponto desejado, onde será
'largado'.
O
comando de 'pegar' só deve funcionar quando o helicóptero for o objeto ativo. O carro a ser pego deve ser o mais próximo do helicóptero,
que ainda não é filho do helicóptero.
O programa deve ser alterado de forma a desenhar mais carros, de forma a se ter
10(dez) carros no cenário.
Comandos
de Pegar e Largar
O
comando de pegar deve estar na função que trata as teclas void keyboard (unsigned char key, int x, int y). A versão do código
que foi disponibilizada apenas adiciona o carro 1 no helicóptero, conforme
mostrado a seguir.
case 'p': // faz o helicoptero pegar um dos objetos do cenario
case 'P':
Cenario[0].AdicionaFilho(&Cenario[1]);
break;
Este
código, entretanto, precisa ser alterado para pegar o mais próximo.
Recomenda-se que a função keyboard apenas
altere uma variável que será testada na rotina de desenho void
Desenha(void),
com algo como :
if (tentaPegar)
{
// acha o carro mais próximo do
helicóptero, que nao eh filho.
proximo = MaisProximo(Cenario[0]);
// adiciona o carro mais próximo
como filho do helicoptero
Cenario[0].AdicionaFilho(&Cenario[proximo]);
}
Para
realizar o trabalho será preciso alterar a versão atual do método AdicionaFilho da classe Objeto
de forma a permitir que um objeto passe a ser um 'filho' de outro um objeto. Ao
ser adicionado, o objeto não deve se mover e, quando o pai for movido, ele
deverá se mover junto. Veja os slides da aula para entender como fazer.
Para
ativar o comando de largar, o usuário pressiona a tecla L. O carro a ser
largado deve ser o último que foi pego.
Alteração
de parâmetros de um objeto
Deve
ser possível mover ou girar um objeto, independente do fato de que este objeto
seja ou não filho do helicóptero.
Desenho
dos Objetos
O
processo de desenho dos objetos ocorre através da chamada do método Draw,
da classe Objeto, apresentado a seguir. Note, entretanto, que, quando um
objeto já é filho de outro, o pai deve ser desenhado antes dele e as
transformações do pai devem estar ativas quando do desenho do filho.
Note
que no código disponibilizado, ocorre apenas o desenho de um dos filhos. Isto
precisa ser alterado.
void Objeto::Draw() {
if (eh_filho) {
printf("Metodo Draw:
Voce esta tentando desenhar um FILHO antes de seu PAI\n");
//
Quem deve desenhar um objeto é
return; // Nao desenha se o
objeto eh filho de algum outro
}
glPushMatrix();
glMultMatrixf(&M[0][0]);
switch(tipo) {
case 1:
DesenhaHelicoptero();
if (!Filho) // se não tiver
filhos, entao termina
break;
// libera o filho para desenho
Filho->eh_filho = false;
// Ativa o desenho dos Filhos
Filho->Draw(); // alterar para desenhar todos os filhos
// Bloqueia o filho para desenho
Filho->eh_filho = true;
break;
case 2:
DesenhaCarro();
break;
}
glPopMatrix();
}
FIM.