Ponto V!

Home Matemática e Física Utilizando Motores de Física - Criando um Programa Exemplo
Bruno Crivelari Sanches
Utilizando Motores de Física - Criando um Programa ExemploImprimir
Escrito por Bruno Crivelari Sanches
Índice do Artigo
Utilizando Motores de Física
Criando um Programa Exemplo
Integrando Bullet com Ogre3d
Configurando o Projeto e Download
Todas Páginas

Criando um Programa Exemplo

Para o primeiro projeto com Bullet optei por utilizar o Ogre 3d por motivos como:

  • Manter o foco do artigo na utilização e funcionalidades da Bullet e não em detalhes como configurar modo de vídeo, carregar texturas, gerar malhas, etc.
  • Ogre 3D é um motor simples de usar para construir pequenos exemplos e possuímos uma boa cobertura no PontoV, que pode ser conferida aqui.
  • Independente do modo usado para exibir os itens na tela, a lógica da física é sempre a mesma

Esclarecido os motivos vamos começar o código, que vai ser baseado no código do artigo “Primeiro programa com Ogre 3d”, a diferença maior é que o novo código não vai carregar modelos externos como é feito nesse tutorial, pois a principio vamos trabalhar apenas com formas simples, no nosso caso, apenas cubos e paralelepípedos.

A primeira adição ao código original é o “include” do arquivo de cabeçalho da Bullet:

#include <btBulletDynamicsCommon.h>

Nada demais, vamos então começar a pensar em como gerenciar a física, para manter o projeto mais organizado decidi por criar uma classe chamada de PhysicsManager, que vai gerenciar os objetos da Bullet e cuidar de criar alguns como se fosse uma factory. O primeiro trabalho dessa nova classe é criar os objetos da Bullet que cuida da simulação, isso é mostrado no código a seguir:

class PhysicsManager
{
    private:
        btDiscreteDynamicsWorld *mWorld;
        btCollisionDispatcher *mCollisionDispatcher;

        btDefaultCollisionConfiguration mCollisionConfig;        
        btDbvtBroadphase mBroadphase;
        btSequentialImpulseConstraintSolver mConstraintSolver;

    public:
        PhysicsManager()
        {            
            mCollisionDispatcher = new btCollisionDispatcher(&mCollisionConfig);
    
            mWorld = new btDiscreteDynamicsWorld(mCollisionDispatcher, &mBroadphase, &mConstraintSolver, &mCollisionConfig);
        }

        ~PhysicsManager()
        {            
            delete mWorld;    
            delete mCollisionDispatcher;
        }
};

Nessa versão inicial da classe temos apenas alguns atributos, alocamos alguns dinamicamente para garantirmos a ordem de inicialização deles (poderíamos até nos aventurar com a ordem de inicialização de variáveis membro, mas isso sempre da um bocado de dores de cabeça).

Os atributos que precisamos para fazer a Bullet começar a funcionar são declarados como membros da classe e são listados a seguir:

  • mWorld: ponteiro para uma instância de btDiscreteDynamicsWorld, que ao pé da letra representa um mundo discreto dinâmico. Isso quer dizer que um objeto dessa classe vai simular um mundo físico com objetos em movimento. O discreto aqui quer dizer que a detecção de colisão dos objetos pode sofrer o efeito de tunelamento. Existe um outro tipo de instância dessa classe na Bullet que usa colisão continua que evita esse problema, mas não iremos abordar essa nesse artigo. Outra variação dessa classe é para uso com corpos macios, que também não iremos abordar nesse artigo.
  • mCollisionDispatcher: ponteiro para uma instância de btCollisionDispatcher, uma classe que a grosso modo cuida de dizer como deve ser tratado cada colisão, por exemplo, quando um objeto côncavo colidir com um objeto convexo, essa classe é quem diz qual o algoritmo que deve ser usado para manipular a colisão. Essa classe também cuida de gerenciar os objetos que estão colidindo, callbacks de colisão, etc.
  • mCollisionConfig: uma classe usada para configurar o btCollisionDispatcher, é do tipo btDefaultCollisionConfiguration e cuida de fornecer ao btCollisionDispatcher informações e serviços como tamanho de certos buffer, pools para alocação de objetos usados durante as colisões, etc.
  • mBroadphase: um objeto do tipo btDbvtBroadphase que é quem cuida da primeira fase da detecção de colisão, o trabalho desse objeto pode ser resumido a identificar objetos que estejam colidindo ou prestes a colidir, esses objetos vão ser passados ao btCollisionDispatcher que vai então decidir quais objetos realmente colidem e por fim tratar das colisões. Esse objeto especificamente faz o trabalho usando duas árvores de volumes hierárquicos que trabalham com AABB, existem outras implementações que utilizam outras técnicas, mas esta é a mais recomendada para mundos onde existem vários objetos dinâmicos se movendo e a remoção e adição de objetos é frequente.

Com estes cinco objetos podemos finalmente criar o mundo da Bullet e finalmente teremos nosso sistema de física pronto para ser usado, isso é feito no construtor da classe com o código abaixo:

mCollisionDispatcher = new btCollisionDispatcher(&mCollisionConfig);
   
mWorld = new btDiscreteDynamicsWorld(mCollisionDispatcher, &mBroadphase, &mConstraintSolver, &mCollisionConfig);

Primeiramente criamos a instância do btCollisionDispatcher usando como parâmetro a configuração do sistema de colisão e na sequência criamos o mundo onde a física será simulada.

Observe que a Bullet é bem flexível e modular, sendo possível customizar varias partes do motor sem que seja necessário modificar o código fonte do mesmo.

Por fim temos o destrutor do PhysicsManager, onde cuidamos de destruir os objetos alocados dinamicamente.

Objetos de Colisão

Como já temos nosso mundo físico criado, podemos começar a pensar em criar os objetos para popular ele. O primeiro item que precisamos definir para um objeto do mundo físico é como vai ser sua forma física, se vamos ter caixas, cilindros, esferas ou quem sabe, uma malha representando um objeto 3d.

Na Bullet os objetos de colisão são representados pela classe btCollisionShape, esta possui varias classes derivadas que representam as mais diversas formas de colisão, como podemos ver no diagrama abaixo:

Um detalhe importante sobre as formas de colisão é que elas podem ser compartilhadas entre corpos rígidos, ou seja, se você precisar criar 10 cilindros com a mesma dimensão, não é preciso criar 10 instâncias de btCollisionShape, pode-se criar apenas uma e usar com todos os corpos rígidos.

Para lidar com as formas de colisão iremos fazer um pequeno gerenciador destes dentro do PhysicsManager, iremos então armazenar as instâncias criadas dentro de um std::vector e no destrutor cuidaremos de destruir todas elas, primeiramente iremos adicionar aos atributos do PhysicsManager a declaração:

std::vector<btCollisionShape*> mCollisionShapes;

Modificaremos então o destrutor para quando o PhysicsManager for destruído, este cuide de destruir também as formas de colisão, isso pode ser feito com o código a seguir:

~PhysicsManager()
{
    for(int i = 0, len = mCollisionShapes.size();i < len; ++i)
        delete mCollisionShapes[i];

    delete mWorld;    
    delete mCollisionDispatcher;
}

Neste artigo iremos utilizar apenas a btBoxShape que é usada como o nome já sugere, para criarmos caixas no mundo físico, adicionemos um método para criar caixas:

btCollisionShape &createBoxShape(float x, float y, float z)
{
    btCollisionShape *shape = new btBoxShape(btVector3(x / 2, y / 2, z / 2));
    mCollisionShapes.push_back(shape);

    return *shape;
}

O método é bem simples, recebe como parâmetros 3 valores, chamados de x, y, e z, que são usados para representar as dimensões da caixa. O construtor da btBoxShape recebe como parâmetro “metade” da dimensão da caixa em cada eixo, por isso que dividimos o valor por 2 durante a criação da instância de btBoxShape. Depois de criado o objeto, o inserimos no vector e devolvemos uma referência para quem invocou o método.

Os valores usados para representar a dimensão da caixa dependem da sua aplicação, o valor 1, por exemplo, pode representar 1m ou 1cm, cabe a sua aplicação definir com que escala ela vai trabalhar.

Criando Corpos Rígidos

Como já temos como criar formas de colisão para os objetos, precisamos agora criar os corpos rígidos, para tal vamos adicionar um novo método ao PhysicsManager que vai criar um corpo rígido, que podemos ver abaixo:

btRigidBody &createBody(const btTransform &transform, float mass, btCollisionShape &shape)
{
    bool isDynamic = (mass != 0.0f);

    btVector3 localInertia(0,0,0);

    if (isDynamic)
        shape.calculateLocalInertia(mass,localInertia);

    btDefaultMotionState* myMotionState = new btDefaultMotionState(transform);

    btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,myMotionState,&shape,localInertia);
    btRigidBody* body = new btRigidBody(rbInfo);
    
    mWorld->addRigidBody(body);

    return *body;
}

O método recebe três parâmetros, sendo eles:

  • transform: uma referência para um btTransform, que é uma classe da Bullet usada para representar transformações, ela armazena as rotações e posição do objeto no espaço. Para conhecer um pouco mais sobre transformações recomendo a leitura deste artigo e deste outro.
  • mass: um número de ponto flutuante que indica a massa do objeto, novamente cabe a sua aplicação definir o que este valor representa, geralmente quilos.
  • shape: uma referência para a forma de colisão que este objeto irá usar.

A primeira operação do método é descobrir se o objeto sendo representado é dinâmico ou não, no caso da Bullet, todo objeto com massa zero é considerado estático, ou seja, não importa o que aconteça, ele nunca irá se mover. Esse tipo de objeto é utilizado para construirmos as partes fixas dos cenários de um jogo, como paredes, chão, etc.

Caso o objeto seja dinâmico, precisamos calcular seu tensor de inércia, que é um vetor que representa matematicamente como o objeto se comporta com forças angulares, forças essas que fazem com que o objeto gire. Por exemplo, um lápis gira facilmente no seu eixo longitudinal, mas se tentarmos girar ele em outro eixo a resistência a rotação é maior devido ao seu comprimento.

Para simplificar o processo as formas de colisão da Bullet já calculam o vetor de inércia de um corpo e isso é feito com o código abaixo:

btVector3 localInertia(0,0,0);

if (isDynamic)
   shape.calculateLocalInertia(mass,localInertia);

O código acima possui um detalhe que pode passar despercebido pela maioria: repare que o vetor localInertia é inicializado explicitamente para zero, isso é feito devido a todos os objetos matemáticos da Bullet não possuírem valores default, ou seja, se fizermos apenas:

btVector3 localInertia;

Os valores x, y e z do vetor não vão ser inicializados e vão ter inicialmente valores indefinidos.

Após calcular o vetor de inércia, precisamos criar uma instância de btMotionState, esta classe tem como objetivo ser uma interface entre um motor gráfico e o sistema de física, ela sempre contém a versão mais atual das transformações de um corpo rígido e pode ser usado por um motor gráfico para atualizar as transformações dos objetos visuais.

É possível criar seu próprio btMotionState para fazer uma acoplagem mais direta com um motor gráfico, no nosso caso iremos usar apenas a versão padrão para simplificar o artigo, sendo esta operação realizada pela linha abaixo:

btDefaultMotionState* myMotionState = new btDefaultMotionState(transform);

Repare que o btDefaultMotionState foi inicializado com transform, que é a transformação que o método recebeu como parâmetro, essa transformação é usada como transformação inicial do corpo rígido.

Agora podemos finalmente criar o corpo rígido, para isso preenchemos um objeto do tipo btRigidBody::btRigidBodyConstructionInfo, que contém todos os parâmetros necessários para a construção do corpo, além da transformação, existem outros parâmetros usados para controlar, por exemplo:

  • fricção: indica um fator de atrito para o corpo quando ele estiver “esfregando” em outro corpo ou outro corpo “esfregar” nele.
  • damping: existe o linear e o angular, este valor é usado para que o objeto tenha uma perda de velocidade com o passar do tempo. Se este valor for zero, um objeto rodando no ar, por exemplo, pode ficar eternamente nessa condição. Podemos ver o damping como uma representação bem aproximada do atrito do objeto com o ar.

Vamos então criar o corpo rígido e adicioná-lo ao mundo físico:

btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,myMotionState,&shape,localInertia);
btRigidBody* body = new btRigidBody(rbInfo);
    
mWorld->addRigidBody(body);

Para utilizar o PhysicsManager e criar um corpo rígido podemos fazer um código como abaixo:

PhysicsManager manager;

btCollisionShape &shape = manager.createBoxShape(1, 1, 1);
btRigidBody &body = manager.createBody(btTransform(btQuaternion::getIdentity(), btVector3(0, 0, 0)), 1, shape);

Por fim, é preciso modificar o PhysicsManager para que ele destrua os corpos rígidos criados, modificaremos o construtor novamente para adicionar o código abaixo:

for (int i=mWorld->getNumCollisionObjects()-1; i>=0 ;i--)
{
    btCollisionObject* obj = mWorld->getCollisionObjectArray()[i];
    btRigidBody* body = btRigidBody::upcast(obj);
    if (body && body->getMotionState())
    {
        delete body->getMotionState();
    }
    mWorld->removeCollisionObject( obj );
    delete obj;
}

O código acima acessa um vetor de objetos do mundo da Bullet, percorre objeto por objeto, caso ele seja um corpo rígido, libera a memória do seu btMotionState. Depois remove o objeto da lista do mundo físico e por fim destrói o objeto usando delete.

Atualizando a Simulação

Para que a simulação aconteça é necessário invocar o método stepSimulation da classe btDiscreteDynamicsWorld. Este método é quem faz a simulação física acontecer movimentando os objetos, tratando colisões e atualizando o btMotionState de cada um, sua invocação é bem simples e fazemos usando o método abaixo:

void update(float ticks)
{
    mWorld->stepSimulation(ticks);
}


Comentários (15)
  • Luis Eduardo Nery Santos  - Visual Studio 2010
    avatar

    Olá, Bruno.

    Estou criando um jogo estilo Arkanoid, como projeto na faculdade.

    Gostaria de saber se essa biblioteca é indicada para utilizar em jogos 2d, feitos com c++ e OpenGL e também estou com duvidas na hora de configurar a variavel de ambiente no visual studio 2010, pois o VC++ não está habilitado.

    Parabéns pelos posts.

    Obrigado. =)

  • Vinícius Godoy de Mendonça
    avatar

    Para jogos 2D provavelmente será mais fácil utilizar a Box2D:
    http://box2d.org/

    O VC++ só será habilitado depois que você criar o primeiro arquivo .cpp dentro do seu projeto. Crie ele e o menu aparece. :)

  • Luis Eduardo Nery Santos  - Instalação da biblioteca
    avatar

    Valeu Vinicius! vou utilizar essa biblioteca para fazer meu jogo então.

    Mas eu não sei muito bem como instalar bibliotecas, tem algum tutorial pra me indicar?

    ou se for simples tem como dar uma explicada resumida?

    Obrigado. :lol:

  • Bruno Crivelari Sanches
    avatar

    Luis,

    no geral bibliotecas C/C++ seguem sempre um mesmo padrão:
    - extrair os arquivos para algum lugar
    - configurar diretório de includes do compilador
    - configurar diretório de libs do linker
    - nas configurações do seu projeto, adicionar as bibliotecas novas como dependencias

    Nesse artigo é feito tudo isso e ainda inclui o passo extra de compilar a bilbioteca. O processo pode variar uma biblioteca para outra, mas os passos básicos são os que listei aqui.

    Esse outro artigo mostra o processo para uma outra biblioteca (SDL) e explica em mais detalhes alguns conceitos, serve como uma "receita" para instalação de bibliotecas:
    http://pontov.com.br/site/cpp/46-conceitos-basicos/1 55-como-usar-bibliotecas-cc

  • augustowebd  - Versão para impressão
    avatar

    Bruno mais uma vez parabéns pelo excelente artigo, só gostaria de solicitar, não querendo ser abusado, uma versão para impressão completo, porque a versão para impressão atual pega apenas a página corrente.

    Grato.

  • Bruno Crivelari Sanches
    avatar

    Obrigado Augusto!

    Quando clico ali no "Todas as páginas" e depois na impressorinha, funciona com todas as páginas.

    T+

  • Napoleon  - Oiii
    avatar

    :cheer: Ei eu gostei muito de sua matéria, tem como falar do box2D :woohoo:

  • Bruno Crivelari Sanches
    avatar

    Oi Napoleon, vou por na lista aqui! :)

  • japaseki
    avatar

    Fala Sanches, estou tentando executar este projeto mas quando ele entra na tela para escolher o render (opengl ou DIRECTX), ele cai automaticamente....o q poderia ser ?

  • Bruno Crivelari Sanches
    avatar

    Olá,

    você seguiu a risca as instruções da ultima página no ultimo item "Configurando o Projeto" ?

    Se sim, tem que depurar para ver onde ocorre o erro e o porque.

    Para saber como usar o depurador: http://www.pontov.com.br/site/cpp/41-visual-c/216-como-usar-o-depurado r-do-visual-c

    T+

  • matheus bueno
    avatar

    ola,


    parabens pelo post, muito bom mesmo,

    fugindo um, pouco do assunto, eu estou criando um jogo( na verdadde é uma especie d PacMan modficado) em allegro, tem como usar a box 2D junto com o allegro, e se tiver, compensa usar?

    obriigado.

  • Bruno Crivelari Sanches
    avatar

    Para um pacman? Não vejo porque usar biblioteca de física. É um sistema de colisão tão simples.

  • Matheus Bueno
    avatar

    no caso haveriam modificações nele, seria meio temático, por exemplo em uma faze com o tema espaço sideral os fantasmas seriam et's que disparariam lazer's e por ai vai, o pacman também ganharia up grades de acordo com os pontos acumulados, esses up grades poderiam ser, por exemplo, mais tempo invencivel quando se come uma pirula, ou almento na velocidade,pensei q em algumas ocasioens aplicar uma biblioteca de física facilitaria meu trabalho.

    o problema maior no jogo esta sendo a AI, mas eu ta pensando em criar um grafo pra representar o labirinto, e calcular o menor caminho entre o fantasma e o pacman, só q eu to em duvida, se isso vai pesar meu jogo(se existe uma solução melhor ficaria grato em ouvir).

    quanto a biblioteca de fisica, gostaria de saber apenas se existe essa possibilidade, ate pra outros jogos com allegro, o pacman é meu primeiro jogo, é mais pra aprendizado, mas eu gostaria de fazer direitinho.

  • Bruno Crivelari Sanches
    avatar

    Pesar? Depende de onde você vai rodar, se for para um PC, um pacman dificilmente vai pesar.

    Sobre a física não vejo dificuldades em integrar com Allegro, geralmente as bibliotecas são independentes de API, como foi mostrado no artigo.

    T+

  • Aml  - Octree e outros
    avatar

    Bruno, antes de mais nada, Parabéns pelos artigos.
    Eu vou utilizar a engine, mas fiquei com uma dúvida:
    Bullet já trabalha com Octree ou é necessário implementar em caso de múltiplos elementos no cenário?


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