Ponto V!

Home Arquitetura Programação Animação por Vértices - Lendo Arquivo Md2
Bruno Crivelari Sanches
Animação por Vértices - Lendo Arquivo Md2Imprimir
Escrito por Bruno Crivelari Sanches
Í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

Lendo e Carregando modelos Md2

Para começar a carga do modelos vamos primeiro dar uma olhada em como fica a implementação do construtor da classe Model:

Model::Model(const string& filename, const string& texFilename, float loadScale): 
    currentAnimation(NULL), 
    texture(texFilename),
    fInterpolate(true)
{
    load(filename, loadScale);
}

O construtor é bem simples: ele inicializa os atributos da classe, se encarrega de carregar a textura usando a classe Texture (os detalhes desta não são discutidos aqui) e invoca o método load, que é quem faz o trabalho de carregar o modelo.

O inicio do método load pode ser visto abaixo:

void Model::load(const string& filename, float loadScale)
{
    /* ------------- STEP 1. READ FILE INFORMATION ------------- */
    Md2Header_t header;

    std::ifstream file (filename.c_str (), std::ios::binary);

    if (file.fail())
        throw std::runtime_error("Couldn't open file: '" + filename + "'");

    // Read header
    file.read (reinterpret_cast(&header), sizeof (Md2Header_t));

    if (header.ident != MD2_MAGIC_NUMBER)
        throw std::runtime_error("Invalid magic number when loading '" + filename + "' md2 file!");

    if (header.version != MD2_VERSION)
        throw std::runtime_error("Invalid md2 version in '" + filename + "' file!");

Primeiramente é criado uma ifstream que será usada para ler os dados do arquivo, na seqüencia é verificado se o arquivo foi aberto corretamente e depois o cabeçalho do arquivo md2 é lido (sendo armazenado na variável header), após carregado o cabeçalho é checado se este é mesmo um arquivo md2 e se a versão do mesmo é a que o programa suporta.

Caso não entenda o porque do reinterpret_cast, vale a pena dar uma lida no artigo: “C++ Type Casting – 2ª Parte”.

Tendo lido o cabeçalho do arquivo é possível começar a carregar o modelo para a memória, sendo assim o código passa a ler as animações e os quadros de cada animação utilizando o código abaixo:

//Read frames and animations
file.seekg (header.offsetFrames, std::ios::beg);
for(int i = 0;i < header.numFrames; ++i)
{
    Md2Frame_t frame;

    file.read(reinterpret_cast(&frame), sizeof(frame));

    Animation* animation = createAnimationIfNeed(frame.name);
    animation->readFrame(file, header.numVertices, frame, loadScale);
}

Primeiramente o ponteiro do arquivo é posicionado na posição onde estão os quadros, aqui vemos então como os dados do cabeçalho são utilizados. Tendo posicionado o ponteiro de leitura do arquivo, é executado um loop e cada quadro é carregado utilizando a estrutura Md2Frame_t, que foi mostrada no inicio do artigo.

Um método importante aqui é o createAnimationIfNeed, este método se encarrega de verificar se já existe uma animação para o quadro, caso não exista ele cria uma nova animação, caso contrário a animação existente é usada. Lembre-se que como foi dito anteriormente o arquivo md2 não armazena as animações de forma separada, ele armazena apenas uma lista com os quadros de todas as animações e usamos o nome dos quadros para tentar identificar as animações. De posse da instância da classe Animation que deve ser usada, pedimos a esta que carregue o quadro de animação e armazene este em sua lista de quadros (utilizando o método animation->readFrame).

Abaixo temos o código do método “createAnimationIfNeed”:

Animation* Model::createAnimationIfNeed(const char* frameName)
{
    string name(frameName);

    int pos = name.find_first_of("0123456789");
    name = name.substr(0, pos);

    //No animation with this name?
    //Create one and add it to the map
    AnimationMap_t::iterator it = animations.find(name);
    if (it == animations.end())
    {
        Animation* animation = new Animation(name);
        animations.insert(std::make_pair(name, animation));        
        if (currentAnimation == NULL)
            currentAnimation = animation;

        return animation;
    }

    //Return the animation
    return it->second;
}

O método primeiramente procura pelo número do quadro e o remove, desta forma isolamos apenas o nome da animação, exemplo: se o quadro se chamar stand01, iremos remover o número e teremos a animação “stand”. Com este nome é feita uma busca no mapa animações, caso a animação não exista é criada uma nova e esta é inserida no mapa.

Voltando ao código do método load vamos então na seqüência carregar os comandos OpenGL para a memória, isto é feito no código abaixo:

// Read OpenGL commands
std::vector glCmds(header.numGlCmds);   
file.seekg (header.offsetGlCmds, std::ios::beg);
file.read (reinterpret_cast(&glCmds[0]), sizeof (GlCmd) * header.numGlCmds);

file.close();

Criamos um vetor para armazenar todos os comandos usando GlCmd (que é apenas um typedef para int). O vetor já é criado com espaço suficiente para todos os comandos do arquivo, é feito então o seekg (para posicionar o ponteiro de leitura do arquivo) e finalmente lemos os comandos para a memória. A partir deste ponto não precisamos mais acessar o arquivo por isso o mesmo é fechado.

Agora que temos os dados do bloco de comandos na memória vamos decodificar estes usando o código abaixo:

int *pGlCmds = &glCmds[0];
int number = glCmds[0];
pGlCmds++;

while (number != 0)
{
    GLenum kind;

    if (number > 0)
        kind = GL_TRIANGLE_STRIP;
    else
    {
        kind = GL_TRIANGLE_FAN;
        number = -number;
    }

    GlCommands command(kind);

    for (int j = 0; j < number; j++)
    {
        Md2Glcmd_t* cmd = reinterpret_cast(pGlCmds);
        command.add(GlCommandVertex(cmd->s, cmd->t, cmd->index));
        pGlCmds += 3; //Since we read integers, jump the index 3 positions forward.
    }

    commands.push_back(command);

    number = *pGlCmds; //Read the next number of commands.
    pGlCmds++; //Advance another position.
}

No código acima o vetor de comandos é percorrido, devemos lembrar aqui que o primeiro valor tem duplo sentido, se negativo indica um TRIANGLE_FAN, se positivo TRIANGLE_STRIP, sendo que o valor do número nos diz quantos comandos temos para esta primitiva. Para cada seqüência criamos uma instância de GlCommands e vamos adicionando vértices conforme o necessário.

Dentro do loop onde cada GlCommandVertex é criado, apenas fazemos um cast para a estrutura Md2Glcmd_t e adicionamos um novo GlCommandVertex ao GlCommands.

Após adicionados todos os vértices ao GlCommands colocamos ele no vetor de commands e passamos a ler a próxima seqüência.

Com isso temos quase tudo o que precisamos para desenhar os modelos md2, esta faltando apenas mostrar como os quadros de animação são lidos, vamos começar então pelo código do método readFrame da classe Animation:

void Animation::readFrame(std::istream &stream, int numVertices, const Md2Frame_t &frameHeader, float scale)
{
    frames.push_back(Frame(stream, numVertices, frameHeader, scale));
}

Este método apenas adiciona um novo quadro ao vetor de quadros da animação, sendo que este novo quadro é inicializado com a istream e as informações para que ele mesmo leia seus dados do arquivo, a leitura é feita no próprio construtor do quadro usando o código abaixo:

Frame::Frame(std::istream &stream, int numVertices, const Md2Frame_t &frameHeader, float scale):
    vertices(numVertices)
{
    for(int i = 0;i < numVertices; ++i)
    {
        Md2Vertex_t v;

        //read the vertex
        stream.read(reinterpret_cast(&v), sizeof(v));

        vertices[i] = Vertex(
                (v.v[0] * frameHeader.scale[0] + frameHeader.translate[0]) * scale,
                (v.v[1] * frameHeader.scale[1] + frameHeader.translate[1]) * scale,
                (v.v[2] * frameHeader.scale[2] + frameHeader.translate[2]) * scale,
                 v.normalIndex
        );        
    }
}

Sabendo o número de vértices (que é obtido do cabeçalho do arquivo md2) este método percorre todos os vértice e vai lendo um a um. Primeiramente os dados são lidos para a memória utilizando a estrutura Md2Vertex_t, que representa um vértice compactado do modelo. Com o vértice na memória é feita a descompactação do vértice. Observando a estrutura vemos que as coordenadas x, y e z do vértices são armazenas em um unsigned char, para convertemos estas para float basta mutiplica-las pela escala especificada no cabeçalho do quadro e somar com a translação especificada no mesmo, aproveitamos também para multiplicar pela escala utilizada no visualizador.

Agora finalmente temos tudo que é preciso na memória e vamos então ver como desenhar o modelo e executar as animações.



Comentários (9)
  • Vitor Almeida da Silva  - Muito bom
    avatar

    Muito completo o artigo Bruno, parabéns.

  • Bruno Crivelari Sanches
    avatar

    Obrigado Vitor!

  • Vinícius Godoy de Mendonça  - Muito bom!
    avatar

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

  • Bruno Crivelari Sanches
    avatar

    eu andava enferrujado no md2, demorou um pouco para lembrar como funcionavam certas coisas, mesmo usando o seu código :).

  • Bruno Crivelari Sanches
    avatar

    Show Zé! Quem sabe agora você não anima o seu visualizador também? :)

  • Luiz França  - Obrigado
    avatar

    Fala Bruno, eu novo no ramo de games mais tenho uma pena noção.
    trabalho como editor de video para dvds muscias e estou muito interessado no ramo de games.

    muito obrigado e parabéns

  • Victor
    avatar

    Que tipos de buffer você está falando? VBO?

  • Bruno
    avatar

    Sim, esses mesmos.

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter