|
| Índice do Artigo |
|---|
| Animação por Vértices |
| Uma Classe Para Carregar Modelos |
| Lendo Arquivo Md2 |
| Animando e Desenhando os Modelos |
| Construindo um Visualizador |
| Todas Páginas |
No artigo anterior sobre “Técnicas de Animação para jogos 3d” vimos de maneira simplificada como funcionam dois métodos de animação. Neste artigo vamos nos aprofundar no primeiro método (animação por vértices) e aprender como criar um visualizador de animações.
Usaremos como ponto de partida a estrutura de classes mostrada no artigo “Organizando a janela em classes” e criaremos novas classes para carregar modelos do formato md2.
O formato md2 foi criado para o jogo Quake II e foi bem popular na época desse jogo devido a sua simplicidade e a quantidade de jogos que utilizaram este motor.
Apesar da técnica de animação por vértices não ser comum hoje em dia em jogos para PC, ela ainda pode ser usada em maquinas onde existem limitações de hardware (celulares e outros dispositivos portáteis), ela também é interessante para se aprender conceitos básicos de animação (interpolação por exemplo) sem precisar de complicações matemáticas que as outras técnicas costuma exigir.
Formato Md2
O arquivo md2 é dividido em dois blocos principais: cabeçalho e dados. No cabeçalho temos um número magico que é usado para confirmar se o arquivo é mesmo um arquivo md2, além disso o cabeçalho contém informações sobre os dados contidos no arquivo, como número de vértices, números de quadros, etc. Na segunda parte do arquivo temos os dados do modelo.
No código abaixo vemos uma estrutura de dados em C que pode ser usada para ler o cabeçalho do arquivo:
typedef struct
{
int ident; // numero magico, "IDP2"
int version; // versao do Md2, deve ser 8
int skinWidth; // Largura da textura
int skinHeight; // Altura da textura
int framesize; // Tamanho em bytes de um quadro
int numSkins; // Numero de skins
int numVertices; // Numero de vertices por quadro
int numTexCoord; // Numero de coordenadas de textura
int numTriangles; // Numero de triangulos
int numGlCmds; // Numero de comandos opengl
int numFrames; // Numero de quadros
int offsetSkins; // offset para dados do skin
int offsetSt; // offset para coordenadas de textura
int offsetTriangles; // offset para dados do triangulo
int offsetFrames; // offset para os dados dos quadros
int offsetGlCmds; // offset para os comandos openGL
int offsetEnd; // offset para o final do arquivo
} Md2Header_t;
A partir deste ponto assumimos que no compilador utilizado um int tem tamanho de 4 bytes, caso seu compilador use um tamanho diferente será preciso utilizar outro tipo de dado. Outros tamanhos que são assumidos ao longo deste artigo:
- int: 4 bytes
- float: 4 bytes
- char: 1 byte
- short: 2 bytes
Após o cabeçalho do arquivo md2 temos os dados do modelo. Para melhor organizar o arquivo este é dividido em blocos e cada bloco contém um tipo de dado, como se cada bloco fosse uma tabela em um banco de dados. O cabeçalho além de nos dar informações para provar que o arquivo é mesmo um arquivo md2 fornece dados sobre cada um desses blocos, dizendo seu tamanho e sua localização dentro do arquivo.
Os primeiros quatro bytes do arquivo possuem o identificador (ou número magico) e deve ser sempre a string “IDP2”, caso o valor seja outro o arquivo não é um md2. Em seguida temos a versão do arquivo, a que utilizamos no artigo é a 8, que é a versão utilizada pelo Quake II.
Não será preciso usar todos os dados do modelo para construir o visualizador, por isso iremos considerar aqui apenas os dados que o visualizador precisa, veremos a seguir os dados que iremos considerar.
Quadros de Animação
O primeiro bloco a ser lido é o bloco que contém os dados dos quadros, este é especificado pelos campos numFrames e offsetFrames, que indicam a quantidade de quadros e a posição destes dentro do arquivo, cada quadro é representado pela estrutura abaixo:
struct Md2Frame_t
{
float scale[3]; // Escala do quadro
float translate[3]; // Translação
char name[16]; // nome do quadro
} ;
Note que cada quadro possui apenas um vetor com a escala e um vetor representando a translação do quadro, isso é usado porque os vértices do modelo são compactados e veremos mais a frente como usar esses valores para descompactar cada vértice de um quadro, outro dado relevante é o nome do quadro, que iremos usar para agrupar os quadros de uma animação. Originalmente no jogo Quake as animações eram controladas diretamente no código, para cada modelo existe um arquivo de cabeçalho C com macros indicando cada quadro de animação e estruturas de dados que juntam os quadros de cada animação.
No visualizador iremos tirar proveito do fato dos nomes serem do formato: nome??, onde nome é o nome da animação (exemplo: stand) e ?? o número do quadro, um exemplo de quadros de uma animação: stand00, stand01, stand02, stand03.
Após cada quadro temos no arquivo a seqüência de vértices deste quadro, para saber a quantidade de vértices basta consultar o campo numVertices do cabeçalho do arquivo. Cada vértice é representado pela estrutura a seguir:
typedef struct
{
unsigned char v[3]; // Posicao compactada
unsigned char normalIndex; // Indice do vetor normal
} Md2Vertex_t;
O campo v armazena as coordenadas x, y e z do vértice, para descompactar uma coordenada basta multiplicar cada elemento pela escala do quadro e somar com a translação do quadro.
Comandos OpenGL
Após carregar os quadros iremos carregar os comandos OpenGL. Como o Quake II dava suporte a apenas OpenGL para renderização via hardware muitos de seus arquivos eram otimizados para uso com OpenGL. No caso dos arquivos md2 estes já vem com suporte a algumas primitivas OpenGL, nesse caso GL_TRIANGLE_STRIP e GL_TRIANGLE_FAN. O bloco de comandos é formado por um vetor de elementos de 4 bytes, podendo estes serem números inteiros ou de ponto flutuante.
Para decodificar esse bloco basta ler o primeiro valor, que vai ser sempre um inteiro. Se for um numero positivo temos um GL_TRIANGLE_STRIP, se for negativo um GL_TRIANGLE_FAN (nesse caso, devemos deixar o numero positivo). O número indica a quantidade de comandos que devemos ler, sendo que cada comando é formado por três números, os dois primeiros são as coordenadas de textura do vértice e o ultimo o índice deste vértice, este índice nos indica qual vértice do quadro atual deve ser desenhado, na imagem abaixo podemos ver os dados de um suposto modelo que contém dois conjuntos de primitivas:
-
20/07/2010 20:50:32 |SAdministrator| Vinícius Godoy de Mendonça - Muito bom!

Ótimo artigo. Eu não explicaria melhor.
Embora a classe original tenha sido realmente do meu jogo, meu C++ anda um pouco enferrujado, e você fez ótimos links para os seus artigos.
-
21/07/2010 10:43:07 |Administrator| Bruno Crivelari Sanches

eu andava enferrujado no md2, demorou um pouco para lembrar como funcionavam certas coisas, mesmo usando o seu código
.
-
21/07/2010 10:23:30 |200.202.118.xxx| José Ricardo - Excelente

Fala Bruno. Parabéns pelo artigo. Lembra quando eu escrevi aquele parser de MD3 usando boost::spirit http://www.boost.org/doc/libs/1_43_0/libs/spirit/doc/html/index .html?
Eu não tinha entendido nada da parte gráfica e agora as coisas começam a fazer sentido pra mim.
Valeu!!











Muito completo o artigo Bruno, parabéns.