Skip to main content

Compartiendo geometría entre cuerpos gráficos y físicos

En esta entrada se va a explicar cómo crear cuerpos físicos que tengan la misma geometría que un determinado cuerpo gráfico.

Compartiendo la geometría

Cuando se está creando un escenario en un videojuego, típicamente se quiere que los elementos estáticos tengan cuerpos rígidos que compartan la forma geométrica de los elementos gráficos. Si usamos Bullet, esto es posible implementando la interfaz btStridingMeshInterface. Dicha interfaz ofrece una forma de crear formas de colisión usando btBvhTriangleMeshShape.

Una de las cosas buenas que tiene Ogre para alguien que está empezando a trabajar con él es la cantidad de snippets que se pueden encontrar en la wiki del proyecto. En este caso, se puede encontrar una solución al problema planteado en esta página de la wiki. En dicha página podemos encontrar una implementación de la interfaz de Bullet mencionada anteriormente.

  • Archivo de cabecera:
#ifndef MeshStrider_h__
#define MeshStrider_h__

#include "..\common.h"

/// Shares vertices/indexes between Ogre and Bullet
class MeshStrider : public btStridingMeshInterface{

public:
    MeshStrider( Ogre::Mesh * m = 0 ):mMesh(m){}

    void set( Ogre::Mesh * m ) { ASSERT(m); mMesh = m; }
    // inherited interface
    virtual int        getNumSubParts() const;

    virtual void    getLockedVertexIndexBase(unsigned char **vertexbase, int& numverts,PHY_ScalarType& type, int& stride,unsigned char **indexbase,int & indexstride,int& numfaces,PHY_ScalarType& indicestype,int subpart=0);
    virtual void    getLockedReadOnlyVertexIndexBase(const unsigned char **vertexbase, int& numverts,PHY_ScalarType& type, int& stride,const unsigned char **indexbase,int & indexstride,int& numfaces,PHY_ScalarType& indicestype,int subpart=0) const;

    virtual void    unLockVertexBase(int subpart);
    virtual void    unLockReadOnlyVertexBase(int subpart) const;

    virtual void    preallocateVertices(int numverts);
    virtual void    preallocateIndices(int numindices);
private:
    Ogre::Mesh * mMesh;
};

#endif // MeshStrider_h__
  • Implementación:
#include "common.h"

#include "MeshStrider.h"

int MeshStrider::getNumSubParts() const
{
    int ret = mMesh->getNumSubMeshes();
    ASSERT( ret > 0 );
    return ret;
}

void MeshStrider::getLockedReadOnlyVertexIndexBase(
    const unsigned char **vertexbase,
    int& numverts,
    PHY_ScalarType& type,
    int& stride,
    const unsigned char **indexbase,
    int & indexstride,
    int& numfaces,
    PHY_ScalarType& indicestype,
    int subpart/*=0*/ ) const
{
    Ogre::SubMesh* submesh = mMesh->getSubMesh(subpart);

    Ogre::VertexData* vertex_data = submesh->useSharedVertices ? mMesh->sharedVertexData : submesh->vertexData;

    const Ogre::VertexElement* posElem =
        vertex_data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION);

    Ogre::HardwareVertexBufferSharedPtr vbuf =
        vertex_data->vertexBufferBinding->getBuffer(posElem->getSource());

    *vertexbase =
        reinterpret_cast<unsigned char*>(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
    // There is _no_ baseVertexPointerToElement() which takes an Ogre::Real or a double
    //  as second argument. So make it float, to avoid trouble when Ogre::Real will
    //  be comiled/typedefed as double:
    //Ogre::Real* pReal;
    float* pReal;
    posElem->baseVertexPointerToElement((void*) *vertexbase, &pReal);
    *vertexbase = (unsigned char*) pReal;

    stride = (int) vbuf->getVertexSize();

    numverts = (int) vertex_data->vertexCount;
    ASSERT( numverts );

    type = PHY_FLOAT;

    Ogre::IndexData* index_data = submesh->indexData;
    Ogre::HardwareIndexBufferSharedPtr ibuf = index_data->indexBuffer;

    if (ibuf->getType() == Ogre::HardwareIndexBuffer::IT_32BIT){
        indicestype = PHY_INTEGER;
    }
    else{
        ASSERT(ibuf->getType() == Ogre::HardwareIndexBuffer::IT_16BIT);
        indicestype = PHY_SHORT;
    }

    if ( submesh->operationType == Ogre::RenderOperation::OT_TRIANGLE_LIST ){
        numfaces = (int) index_data->indexCount / 3;
        indexstride = (int) ibuf->getIndexSize()*3;
    }
    else
    if ( submesh->operationType == Ogre::RenderOperation::OT_TRIANGLE_STRIP ){
        numfaces = (int) index_data->indexCount -2;
        indexstride = (int) ibuf->getIndexSize();
    }
    else{
        ASSERT( 0 ); // not supported
    }

    *indexbase = reinterpret_cast<unsigned char*>(ibuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
}

void MeshStrider::getLockedVertexIndexBase( unsigned char **vertexbase, int& numverts,PHY_ScalarType& type, int& stride,unsigned char **indexbase,int & indexstride,int& numfaces,PHY_ScalarType& indicestype,int subpart/*=0*/ )
{
    ASSERT( 0 );
}

void MeshStrider::unLockReadOnlyVertexBase( int subpart ) const
{
    Ogre::SubMesh* submesh = mMesh->getSubMesh(subpart);

    Ogre::VertexData* vertex_data = submesh->useSharedVertices ? mMesh->sharedVertexData : submesh->vertexData;

    const Ogre::VertexElement* posElem =
        vertex_data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION);

    Ogre::HardwareVertexBufferSharedPtr vbuf =
        vertex_data->vertexBufferBinding->getBuffer(posElem->getSource());

    vbuf->unlock();

    Ogre::IndexData* index_data = submesh->indexData;
    Ogre::HardwareIndexBufferSharedPtr ibuf = index_data->indexBuffer;
    ibuf->unlock();
}

void MeshStrider::unLockVertexBase( int subpart )
{
    ASSERT( 0 );
}

void MeshStrider::preallocateVertices( int numverts )
{
    ASSERT( 0 );
}

void MeshStrider::preallocateIndices( int numindices )
{
    ASSERT( 0 );
}

¿Cómo usarlo?

Para usar esta clase en un proyecto, se debe hacer lo siguiente:

  • Crear dos ficheros conteniendo la declaración y la definición de la clase, disponible en los listados de código anteriores y en el enlace a la wiki.
  • Crear un puntero de tipo MeshStrider, pasándole la referencia a la malla de la que se quiera crear una forma física:
Ogre::Root* root = new Ogre::Root();
Ogre::SceneManager* scene_manager = root->getSceneManager();
Ogre::Entity* entity = scene_manager->createEntity("entity_name", "file.mesh");

MeshStrider* strider = new MeshStrider(entity->getMesh().get());

Observar que el método Ogre::Entity::getMesh devuelve Ogre::MeshPtr, que en realidad es una implementación propia de Ogre de un shared_ptr.

  • Crear una forma de colisión de tipo btBvhTriangleMeshShape, pasandole el mesh strider. Después crear el cuerpo físico y añadirlo al mundo:
btCollisionShape* shape = new btBvhTriangleMeshShape(strider, true, true);

btVector3 inertia(0 ,0 ,0);

//Creación típica de un cuerpo rígido en Bullet
btVector3 inertia(0 ,0 ,0);
int mass = 1000;
if(mass != 0)  shape->calculateLocalInertia(mass, inertia);

btQuaternion rotation(btVector3(0, 1, 0), btScalar(0));
btVector3 origin(0, 0, 0);
btTransform transform (origin, rotation);
MotionState* motionState = new MotionState(world_transform, node);

btRigidBody::btRigidBodyConstructionInfo rigidBodyCI(mass, motionState, shape, inertia);

btRigidBody* rigidBody = new btRigidBody(rigidBodyCI);

dynamics_world_->addRigidBody(body);

Con esto ya tendremos un cuerpo rígido con una forma de colisión estática cuya geometría corresponderá con la malla de Ogre que se le haya pasado. Hay que recordar que un cuerpo estático está pensado para colisionar contra cuerpos dinámico, y nunca contra otros cuerpos estáticos. Normalmente lo que suele ocurrir si usamos este método para crear cuerpos que tienen que interaccionar con el entorno, como por ejemplo una pelota, es que se ignoran las colisiones entre cuerpos estáticos.

Por la razón anterior, la geometría estática es muy eficiente a la hora de crear escenarios.

Conclusiones

En este post se ha visto como usar la interfaz btStridingMeshInterface de Bullet para crear cuerpos rígidos con formas de colisión estática que se ajustan a la geometría del cuerpo gráfico que queramos.

Share