diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index a4272d717..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "libs/openengine"] - path = libs/openengine - url = git://github.com/zinnschlag/OpenEngine diff --git a/libs/openengine b/libs/openengine deleted file mode 160000 index 8f9871831..000000000 --- a/libs/openengine +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8f98718315fe11af359740c4a025fd1ca52a9157 diff --git a/libs/openengine/.gitignore b/libs/openengine/.gitignore new file mode 100644 index 000000000..23a5e931b --- /dev/null +++ b/libs/openengine/.gitignore @@ -0,0 +1,3 @@ +*~ +*.o +*_test diff --git a/libs/openengine/README b/libs/openengine/README new file mode 100644 index 000000000..621fe8d60 --- /dev/null +++ b/libs/openengine/README @@ -0,0 +1,12 @@ +OpenEngine README +================= + +OpenEngine is a bunch of stand-alone game engine modules collected from the OpenMW project (see http://github.com/korslund/openmw or http://openmw.com ) and from certain other projects. + +It is currently a very early work in progress, and development will follow OpenMW closely for a while forward. + +OpenEngine will depend heavily on Mangle ( http://github.com/korslund/mangle/ ) and will thus aim to be backend agnostic. When finished it should work with a variety for free and commercial middleware libraries as backends for graphics, sound, physics, input and so on. + +All questions can be directed to Nicolay Korslund at korslund@gmail.com + +- Nicolay diff --git a/libs/openengine/bullet/BtOgre.cpp b/libs/openengine/bullet/BtOgre.cpp new file mode 100644 index 000000000..618739083 --- /dev/null +++ b/libs/openengine/bullet/BtOgre.cpp @@ -0,0 +1,1090 @@ +/* + * ============================================================================================= + * + * Filename: BtOgre.cpp + * + * Description: BtOgre implementation. + * + * Version: 1.0 + * Created: 27/12/2008 01:47:56 PM + * + * Author: Nikhilesh (nikki) + * + * ============================================================================================= + */ + +#include "BtOgrePG.h" +#include "BtOgreGP.h" +#include "BtOgreExtras.h" + +using namespace Ogre; + +namespace BtOgre { + +/* + * ============================================================================================= + * BtOgre::VertexIndexToShape + * ============================================================================================= + */ + + void VertexIndexToShape::addStaticVertexData(const VertexData *vertex_data) + { + if (!vertex_data) + return; + + const VertexData *data = vertex_data; + + const unsigned int prev_size = mVertexCount; + mVertexCount += (unsigned int)data->vertexCount; + + Ogre::Vector3* tmp_vert = new Ogre::Vector3[mVertexCount]; + if (mVertexBuffer) + { + memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size); + delete[] mVertexBuffer; + } + mVertexBuffer = tmp_vert; + + // Get the positional buffer element + { + const Ogre::VertexElement* posElem = data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION); + Ogre::HardwareVertexBufferSharedPtr vbuf = data->vertexBufferBinding->getBuffer(posElem->getSource()); + const unsigned int vSize = (unsigned int)vbuf->getVertexSize(); + + unsigned char* vertex = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + float* pReal; + Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size]; + const unsigned int vertexCount = (unsigned int)data->vertexCount; + for(unsigned int j = 0; j < vertexCount; ++j) + { + posElem->baseVertexPointerToElement(vertex, &pReal); + vertex += vSize; + + curVertices->x = (*pReal++); + curVertices->y = (*pReal++); + curVertices->z = (*pReal++); + + *curVertices = mTransform * (*curVertices); + + curVertices++; + } + vbuf->unlock(); + } + } + + //------------------------------------------------------------------------------------------------ + void VertexIndexToShape::addAnimatedVertexData(const Ogre::VertexData *vertex_data, + const Ogre::VertexData *blend_data, + const Ogre::Mesh::IndexMap *indexMap) + { + // Get the bone index element + assert(vertex_data); + + const VertexData *data = blend_data; + const unsigned int prev_size = mVertexCount; + mVertexCount += (unsigned int)data->vertexCount; + Ogre::Vector3* tmp_vert = new Ogre::Vector3[mVertexCount]; + if (mVertexBuffer) + { + memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size); + delete[] mVertexBuffer; + } + mVertexBuffer = tmp_vert; + + // Get the positional buffer element + { + const Ogre::VertexElement* posElem = data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION); + assert (posElem); + Ogre::HardwareVertexBufferSharedPtr vbuf = data->vertexBufferBinding->getBuffer(posElem->getSource()); + const unsigned int vSize = (unsigned int)vbuf->getVertexSize(); + + unsigned char* vertex = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + float* pReal; + Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size]; + const unsigned int vertexCount = (unsigned int)data->vertexCount; + for(unsigned int j = 0; j < vertexCount; ++j) + { + posElem->baseVertexPointerToElement(vertex, &pReal); + vertex += vSize; + + curVertices->x = (*pReal++); + curVertices->y = (*pReal++); + curVertices->z = (*pReal++); + + *curVertices = mTransform * (*curVertices); + + curVertices++; + } + vbuf->unlock(); + } + + { + const Ogre::VertexElement* bneElem = vertex_data->vertexDeclaration->findElementBySemantic(Ogre::VES_BLEND_INDICES); + assert (bneElem); + + Ogre::HardwareVertexBufferSharedPtr vbuf = vertex_data->vertexBufferBinding->getBuffer(bneElem->getSource()); + const unsigned int vSize = (unsigned int)vbuf->getVertexSize(); + unsigned char* vertex = static_cast(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY)); + + unsigned char* pBone; + + if (!mBoneIndex) + mBoneIndex = new BoneIndex(); + BoneIndex::iterator i; + + Ogre::Vector3 * curVertices = &mVertexBuffer[prev_size]; + + const unsigned int vertexCount = (unsigned int)vertex_data->vertexCount; + for(unsigned int j = 0; j < vertexCount; ++j) + { + bneElem->baseVertexPointerToElement(vertex, &pBone); + vertex += vSize; + + const unsigned char currBone = (indexMap) ? (*indexMap)[*pBone] : *pBone; + i = mBoneIndex->find (currBone); + Vector3Array* l = 0; + if (i == mBoneIndex->end()) + { + l = new Vector3Array; + mBoneIndex->insert(BoneKeyIndex(currBone, l)); + } + else + { + l = i->second; + } + + l->push_back(*curVertices); + + curVertices++; + } + vbuf->unlock(); + } + } + + //------------------------------------------------------------------------------------------------ + void VertexIndexToShape::addIndexData(IndexData *data, const unsigned int offset) + { + const unsigned int prev_size = mIndexCount; + mIndexCount += (unsigned int)data->indexCount; + + unsigned int* tmp_ind = new unsigned int[mIndexCount]; + if (mIndexBuffer) + { + memcpy (tmp_ind, mIndexBuffer, sizeof(unsigned int) * prev_size); + delete[] mIndexBuffer; + } + mIndexBuffer = tmp_ind; + + const unsigned int numTris = (unsigned int) data->indexCount / 3; + HardwareIndexBufferSharedPtr ibuf = data->indexBuffer; + const bool use32bitindexes = (ibuf->getType() == HardwareIndexBuffer::IT_32BIT); + unsigned int index_offset = prev_size; + + if (use32bitindexes) + { + const unsigned int* pInt = static_cast(ibuf->lock(HardwareBuffer::HBL_READ_ONLY)); + for(unsigned int k = 0; k < numTris; ++k) + { + mIndexBuffer[index_offset ++] = offset + *pInt++; + mIndexBuffer[index_offset ++] = offset + *pInt++; + mIndexBuffer[index_offset ++] = offset + *pInt++; + } + ibuf->unlock(); + } + else + { + const unsigned short* pShort = static_cast(ibuf->lock(HardwareBuffer::HBL_READ_ONLY)); + for(unsigned int k = 0; k < numTris; ++k) + { + mIndexBuffer[index_offset ++] = offset + static_cast (*pShort++); + mIndexBuffer[index_offset ++] = offset + static_cast (*pShort++); + mIndexBuffer[index_offset ++] = offset + static_cast (*pShort++); + } + ibuf->unlock(); + } + + } + + //------------------------------------------------------------------------------------------------ + Real VertexIndexToShape::getRadius() + { + if (mBoundRadius == (-1)) + { + getSize(); + mBoundRadius = (std::max(mBounds.x,std::max(mBounds.y,mBounds.z)) * 0.5); + } + return mBoundRadius; + } + + //------------------------------------------------------------------------------------------------ + Vector3 VertexIndexToShape::getSize() + { + const unsigned int vCount = getVertexCount(); + if (mBounds == Ogre::Vector3(-1,-1,-1) && vCount > 0) + { + + const Ogre::Vector3 * const v = getVertices(); + + Ogre::Vector3 vmin(v[0]); + Ogre::Vector3 vmax(v[0]); + + for(unsigned int j = 1; j < vCount; j++) + { + vmin.x = std::min(vmin.x, v[j].x); + vmin.y = std::min(vmin.y, v[j].y); + vmin.z = std::min(vmin.z, v[j].z); + + vmax.x = std::max(vmax.x, v[j].x); + vmax.y = std::max(vmax.y, v[j].y); + vmax.z = std::max(vmax.z, v[j].z); + } + + mBounds.x = vmax.x - vmin.x; + mBounds.y = vmax.y - vmin.y; + mBounds.z = vmax.z - vmin.z; + } + + return mBounds; + } + + //------------------------------------------------------------------------------------------------ + const Ogre::Vector3* VertexIndexToShape::getVertices() + { + return mVertexBuffer; + } + + //------------------------------------------------------------------------------------------------ + unsigned int VertexIndexToShape::getVertexCount() + { + return mVertexCount; + } + + //------------------------------------------------------------------------------------------------ + const unsigned int* VertexIndexToShape::getIndices() + { + return mIndexBuffer; + } + + //------------------------------------------------------------------------------------------------ + unsigned int VertexIndexToShape::getIndexCount() + { + return mIndexCount; + } + + //------------------------------------------------------------------------------------------------ + btSphereShape* VertexIndexToShape::createSphere() + { + const Ogre::Real rad = getRadius(); + assert((rad > 0.0) && + ("Sphere radius must be greater than zero")); + btSphereShape* shape = new btSphereShape(rad); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + btBoxShape* VertexIndexToShape::createBox() + { + const Ogre::Vector3 sz = getSize(); + + assert((sz.x > 0.0) && (sz.y > 0.0) && (sz.y > 0.0) && + ("Size of box must be greater than zero on all axes")); + + btBoxShape* shape = new btBoxShape(Convert::toBullet(sz * 0.5)); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + btCylinderShape* VertexIndexToShape::createCylinder() + { + const Ogre::Vector3 sz = getSize(); + + assert((sz.x > 0.0) && (sz.y > 0.0) && (sz.y > 0.0) && + ("Size of Cylinder must be greater than zero on all axes")); + + btCylinderShape* shape = new btCylinderShapeX(Convert::toBullet(sz * 0.5)); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + btConvexHullShape* VertexIndexToShape::createConvex() + { + assert(mVertexCount && (mIndexCount >= 6) && + ("Mesh must have some vertices and at least 6 indices (2 triangles)")); + + return new btConvexHullShape((btScalar*) &mVertexBuffer[0].x, mVertexCount, sizeof(Vector3)); + } + + //------------------------------------------------------------------------------------------------ + btBvhTriangleMeshShape* VertexIndexToShape::createTrimesh() + { + assert(mVertexCount && (mIndexCount >= 6) && + ("Mesh must have some vertices and at least 6 indices (2 triangles)")); + + unsigned int numFaces = mIndexCount / 3; + + btTriangleMesh *trimesh = new btTriangleMesh(); + unsigned int *indices = mIndexBuffer; + Vector3 *vertices = mVertexBuffer; + + btVector3 vertexPos[3]; + for (unsigned int n = 0; n < numFaces; ++n) + { + { + const Vector3 &vec = vertices[*indices]; + vertexPos[0][0] = vec.x; + vertexPos[0][1] = vec.y; + vertexPos[0][2] = vec.z; + } + { + const Vector3 &vec = vertices[*(indices + 1)]; + vertexPos[1][0] = vec.x; + vertexPos[1][1] = vec.y; + vertexPos[1][2] = vec.z; + } + { + const Vector3 &vec = vertices[*(indices + 2)]; + vertexPos[2][0] = vec.x; + vertexPos[2][1] = vec.y; + vertexPos[2][2] = vec.z; + } + + indices += 3; + + trimesh->addTriangle(vertexPos[0], vertexPos[1], vertexPos[2]); + } + + const bool useQuantizedAABB = true; + btBvhTriangleMeshShape *shape = new btBvhTriangleMeshShape(trimesh, useQuantizedAABB); + + shape->setLocalScaling(Convert::toBullet(mScale)); + + return shape; + } + + //------------------------------------------------------------------------------------------------ + VertexIndexToShape::~VertexIndexToShape() + { + delete[] mVertexBuffer; + delete[] mIndexBuffer; + + if (mBoneIndex) + { + for(BoneIndex::iterator i = mBoneIndex->begin(); + i != mBoneIndex->end(); + ++i) + { + delete i->second; + } + delete mBoneIndex; + } + } + + //------------------------------------------------------------------------------------------------ + VertexIndexToShape::VertexIndexToShape(const Matrix4 &transform) : + mVertexBuffer (0), + mIndexBuffer (0), + mVertexCount (0), + mIndexCount (0), + mTransform (transform), + mBoundRadius (-1), + mBounds (Vector3(-1,-1,-1)), + mBoneIndex (0), + mScale(1) + { + } + +/* + * ============================================================================================= + * BtOgre::StaticMeshToShapeConverter + * ============================================================================================= + */ + + StaticMeshToShapeConverter::StaticMeshToShapeConverter() : + VertexIndexToShape(), + mEntity (0), + mNode (0) + { + } + + //------------------------------------------------------------------------------------------------ + StaticMeshToShapeConverter::~StaticMeshToShapeConverter() + { + } + + //------------------------------------------------------------------------------------------------ + StaticMeshToShapeConverter::StaticMeshToShapeConverter(Entity *entity, const Matrix4 &transform) : + VertexIndexToShape(transform), + mEntity (0), + mNode (0) + { + addEntity(entity, transform); + } + + //------------------------------------------------------------------------------------------------ + StaticMeshToShapeConverter::StaticMeshToShapeConverter(Renderable *rend, const Matrix4 &transform) : + VertexIndexToShape(transform), + mEntity (0), + mNode (0) + { + RenderOperation op; + rend->getRenderOperation(op); + VertexIndexToShape::addStaticVertexData(op.vertexData); + if(op.useIndexes) + VertexIndexToShape::addIndexData(op.indexData); + + } + + //------------------------------------------------------------------------------------------------ + void StaticMeshToShapeConverter::addEntity(Entity *entity,const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + mEntity = entity; + mNode = (SceneNode*)(mEntity->getParentNode()); + mTransform = transform; + mScale = mNode->getScale(); + + if (mEntity->getMesh()->sharedVertexData) + { + VertexIndexToShape::addStaticVertexData (mEntity->getMesh()->sharedVertexData); + } + + for (unsigned int i = 0;i < mEntity->getNumSubEntities();++i) + { + SubMesh *sub_mesh = mEntity->getSubEntity(i)->getSubMesh(); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + VertexIndexToShape::addStaticVertexData (sub_mesh->vertexData); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + } + + //------------------------------------------------------------------------------------------------ + void StaticMeshToShapeConverter::addMesh(const MeshPtr &mesh, const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + //_entity = entity; + //_node = (SceneNode*)(_entity->getParentNode()); + mTransform = transform; + + if (mesh->hasSkeleton ()) + Ogre::LogManager::getSingleton().logMessage("MeshToShapeConverter::addMesh : Mesh " + mesh->getName () + " as skeleton but added to trimesh non animated"); + + if (mesh->sharedVertexData) + { + VertexIndexToShape::addStaticVertexData (mesh->sharedVertexData); + } + + for(unsigned int i = 0;i < mesh->getNumSubMeshes();++i) + { + SubMesh *sub_mesh = mesh->getSubMesh(i); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + VertexIndexToShape::addStaticVertexData (sub_mesh->vertexData); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + } + +/* + * ============================================================================================= + * BtOgre::AnimatedMeshToShapeConverter + * ============================================================================================= + */ + + AnimatedMeshToShapeConverter::AnimatedMeshToShapeConverter(Entity *entity,const Matrix4 &transform) : + VertexIndexToShape(transform), + mEntity (0), + mNode (0), + mTransformedVerticesTemp(0), + mTransformedVerticesTempSize(0) + { + addEntity(entity, transform); + } + + //------------------------------------------------------------------------------------------------ + AnimatedMeshToShapeConverter::AnimatedMeshToShapeConverter() : + VertexIndexToShape(), + mEntity (0), + mNode (0), + mTransformedVerticesTemp(0), + mTransformedVerticesTempSize(0) + { + } + + //------------------------------------------------------------------------------------------------ + AnimatedMeshToShapeConverter::~AnimatedMeshToShapeConverter() + { + delete[] mTransformedVerticesTemp; + } + + //------------------------------------------------------------------------------------------------ + void AnimatedMeshToShapeConverter::addEntity(Entity *entity,const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + mEntity = entity; + mNode = (SceneNode*)(mEntity->getParentNode()); + mTransform = transform; + + assert (entity->getMesh()->hasSkeleton ()); + + mEntity->addSoftwareAnimationRequest(false); + mEntity->_updateAnimation(); + + if (mEntity->getMesh()->sharedVertexData) + { + VertexIndexToShape::addAnimatedVertexData (mEntity->getMesh()->sharedVertexData, + mEntity->_getSkelAnimVertexData(), + &mEntity->getMesh()->sharedBlendIndexToBoneIndexMap); + } + + for (unsigned int i = 0;i < mEntity->getNumSubEntities();++i) + { + SubMesh *sub_mesh = mEntity->getSubEntity(i)->getSubMesh(); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + + VertexIndexToShape::addAnimatedVertexData (sub_mesh->vertexData, + mEntity->getSubEntity(i)->_getSkelAnimVertexData(), + &sub_mesh->blendIndexToBoneIndexMap); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + + mEntity->removeSoftwareAnimationRequest(false); + } + + //------------------------------------------------------------------------------------------------ + void AnimatedMeshToShapeConverter::addMesh(const MeshPtr &mesh, const Matrix4 &transform) + { + // Each entity added need to reset size and radius + // next time getRadius and getSize are asked, they're computed. + mBounds = Ogre::Vector3(-1,-1,-1); + mBoundRadius = -1; + + //_entity = entity; + //_node = (SceneNode*)(_entity->getParentNode()); + mTransform = transform; + + assert (mesh->hasSkeleton ()); + + if (mesh->sharedVertexData) + { + VertexIndexToShape::addAnimatedVertexData (mesh->sharedVertexData, + 0, + &mesh->sharedBlendIndexToBoneIndexMap); + } + + for(unsigned int i = 0;i < mesh->getNumSubMeshes();++i) + { + SubMesh *sub_mesh = mesh->getSubMesh(i); + + if (!sub_mesh->useSharedVertices) + { + VertexIndexToShape::addIndexData(sub_mesh->indexData, mVertexCount); + + VertexIndexToShape::addAnimatedVertexData (sub_mesh->vertexData, + 0, + &sub_mesh->blendIndexToBoneIndexMap); + } + else + { + VertexIndexToShape::addIndexData (sub_mesh->indexData); + } + + } + } + + //------------------------------------------------------------------------------------------------ + bool AnimatedMeshToShapeConverter::getBoneVertices(unsigned char bone, + unsigned int &vertex_count, + Ogre::Vector3* &vertices, + const Vector3 &bonePosition) + { + BoneIndex::iterator i = mBoneIndex->find(bone); + + if (i == mBoneIndex->end()) + return false; + + if (i->second->empty()) + return false; + + vertex_count = (unsigned int) i->second->size() + 1; + if (vertex_count > mTransformedVerticesTempSize) + { + if (mTransformedVerticesTemp) + delete[] mTransformedVerticesTemp; + + mTransformedVerticesTemp = new Ogre::Vector3[vertex_count]; + + } + + vertices = mTransformedVerticesTemp; + vertices[0] = bonePosition; + //mEntity->_getParentNodeFullTransform() * + //mEntity->getSkeleton()->getBone(bone)->_getDerivedPosition(); + + //mEntity->getSkeleton()->getBone(bone)->_getDerivedOrientation() + unsigned int currBoneVertex = 1; + Vector3Array::iterator j = i->second->begin(); + while(j != i->second->end()) + { + vertices[currBoneVertex] = (*j); + ++j; + ++currBoneVertex; + } + return true; + } + + //------------------------------------------------------------------------------------------------ + btBoxShape* AnimatedMeshToShapeConverter::createAlignedBox(unsigned char bone, + const Vector3 &bonePosition, + const Quaternion &boneOrientation) + { + unsigned int vertex_count; + Vector3* vertices; + + if (!getBoneVertices(bone, vertex_count, vertices, bonePosition)) + return 0; + + Vector3 min_vec(vertices[0]); + Vector3 max_vec(vertices[0]); + + for(unsigned int j = 1; j < vertex_count ;j++) + { + min_vec.x = std::min(min_vec.x,vertices[j].x); + min_vec.y = std::min(min_vec.y,vertices[j].y); + min_vec.z = std::min(min_vec.z,vertices[j].z); + + max_vec.x = std::max(max_vec.x,vertices[j].x); + max_vec.y = std::max(max_vec.y,vertices[j].y); + max_vec.z = std::max(max_vec.z,vertices[j].z); + } + const Ogre::Vector3 maxMinusMin(max_vec - min_vec); + btBoxShape* box = new btBoxShape(Convert::toBullet(maxMinusMin)); + + /*const Ogre::Vector3 pos + (min_vec.x + (maxMinusMin.x * 0.5), + min_vec.y + (maxMinusMin.y * 0.5), + min_vec.z + (maxMinusMin.z * 0.5));*/ + + //box->setPosition(pos); + + return box; + } + + //------------------------------------------------------------------------------------------------ + bool AnimatedMeshToShapeConverter::getOrientedBox(unsigned char bone, + const Vector3 &bonePosition, + const Quaternion &boneOrientation, + Vector3 &box_afExtent, + Vector3 *box_akAxis, + Vector3 &box_kCenter) + { + unsigned int vertex_count; + Vector3* vertices; + + if (!getBoneVertices(bone, vertex_count, vertices, bonePosition)) + return false; + + box_kCenter = Vector3::ZERO; + + { + for(unsigned int c = 0 ;c < vertex_count;c++) + { + box_kCenter += vertices[c]; + } + const Ogre::Real invVertexCount = 1.0 / vertex_count; + box_kCenter *= invVertexCount; + } + Quaternion orient = boneOrientation; + orient.ToAxes(box_akAxis); + + // Let C be the box center and let U0, U1, and U2 be the box axes. Each + // input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. The + // following code computes min(y0), max(y0), min(y1), max(y1), min(y2), + // and max(y2). The box center is then adjusted to be + // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + + // 0.5*(min(y2)+max(y2))*U2 + + Ogre::Vector3 kDiff (vertices[1] - box_kCenter); + Ogre::Real fY0Min = kDiff.dotProduct(box_akAxis[0]), fY0Max = fY0Min; + Ogre::Real fY1Min = kDiff.dotProduct(box_akAxis[1]), fY1Max = fY1Min; + Ogre::Real fY2Min = kDiff.dotProduct(box_akAxis[2]), fY2Max = fY2Min; + + for (unsigned int i = 2; i < vertex_count; i++) + { + kDiff = vertices[i] - box_kCenter; + + const Ogre::Real fY0 = kDiff.dotProduct(box_akAxis[0]); + if ( fY0 < fY0Min ) + fY0Min = fY0; + else if ( fY0 > fY0Max ) + fY0Max = fY0; + + const Ogre::Real fY1 = kDiff.dotProduct(box_akAxis[1]); + if ( fY1 < fY1Min ) + fY1Min = fY1; + else if ( fY1 > fY1Max ) + fY1Max = fY1; + + const Ogre::Real fY2 = kDiff.dotProduct(box_akAxis[2]); + if ( fY2 < fY2Min ) + fY2Min = fY2; + else if ( fY2 > fY2Max ) + fY2Max = fY2; + } + + box_afExtent.x = ((Real)0.5)*(fY0Max - fY0Min); + box_afExtent.y = ((Real)0.5)*(fY1Max - fY1Min); + box_afExtent.z = ((Real)0.5)*(fY2Max - fY2Min); + + box_kCenter += (0.5*(fY0Max+fY0Min))*box_akAxis[0] + + (0.5*(fY1Max+fY1Min))*box_akAxis[1] + + (0.5*(fY2Max+fY2Min))*box_akAxis[2]; + + box_afExtent *= 2.0; + + return true; + } + + //------------------------------------------------------------------------------------------------ + btBoxShape *AnimatedMeshToShapeConverter::createOrientedBox(unsigned char bone, + const Vector3 &bonePosition, + const Quaternion &boneOrientation) + { + Ogre::Vector3 box_akAxis[3]; + Ogre::Vector3 box_afExtent; + Ogre::Vector3 box_afCenter; + + if (!getOrientedBox(bone, bonePosition, boneOrientation, + box_afExtent, + box_akAxis, + box_afCenter)) + return 0; + + btBoxShape *geom = new btBoxShape(Convert::toBullet(box_afExtent)); + //geom->setOrientation(Quaternion(box_akAxis[0],box_akAxis[1],box_akAxis[2])); + //geom->setPosition(box_afCenter); + return geom; + } + +/* + * ============================================================================================= + * BtOgre::DynamicRenderable + * ============================================================================================= + */ + + DynamicRenderable::DynamicRenderable() + { + } + + //------------------------------------------------------------------------------------------------ + DynamicRenderable::~DynamicRenderable() + { + delete mRenderOp.vertexData; + delete mRenderOp.indexData; + } + + //------------------------------------------------------------------------------------------------ + void DynamicRenderable::initialize(RenderOperation::OperationType operationType, + bool useIndices) + { + // Initialize render operation + mRenderOp.operationType = operationType; + mRenderOp.useIndexes = useIndices; + mRenderOp.vertexData = new VertexData; + if (mRenderOp.useIndexes) + mRenderOp.indexData = new IndexData; + + // Reset buffer capacities + mVertexBufferCapacity = 0; + mIndexBufferCapacity = 0; + + // Create vertex declaration + createVertexDeclaration(); + } + + //------------------------------------------------------------------------------------------------ + void DynamicRenderable::prepareHardwareBuffers(size_t vertexCount, + size_t indexCount) + { + // Prepare vertex buffer + size_t newVertCapacity = mVertexBufferCapacity; + if ((vertexCount > mVertexBufferCapacity) || + (!mVertexBufferCapacity)) + { + // vertexCount exceeds current capacity! + // It is necessary to reallocate the buffer. + + // Check if this is the first call + if (!newVertCapacity) + newVertCapacity = 1; + + // Make capacity the next power of two + while (newVertCapacity < vertexCount) + newVertCapacity <<= 1; + } + else if (vertexCount < mVertexBufferCapacity>>1) { + // Make capacity the previous power of two + while (vertexCount < newVertCapacity>>1) + newVertCapacity >>= 1; + } + if (newVertCapacity != mVertexBufferCapacity) + { + mVertexBufferCapacity = newVertCapacity; + // Create new vertex buffer + HardwareVertexBufferSharedPtr vbuf = + HardwareBufferManager::getSingleton().createVertexBuffer( + mRenderOp.vertexData->vertexDeclaration->getVertexSize(0), + mVertexBufferCapacity, + HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); // TODO: Custom HBU_? + + // Bind buffer + mRenderOp.vertexData->vertexBufferBinding->setBinding(0, vbuf); + } + // Update vertex count in the render operation + mRenderOp.vertexData->vertexCount = vertexCount; + + if (mRenderOp.useIndexes) + { + OgreAssert(indexCount <= std::numeric_limits::max(), "indexCount exceeds 16 bit"); + + size_t newIndexCapacity = mIndexBufferCapacity; + // Prepare index buffer + if ((indexCount > newIndexCapacity) || + (!newIndexCapacity)) + { + // indexCount exceeds current capacity! + // It is necessary to reallocate the buffer. + + // Check if this is the first call + if (!newIndexCapacity) + newIndexCapacity = 1; + + // Make capacity the next power of two + while (newIndexCapacity < indexCount) + newIndexCapacity <<= 1; + + } + else if (indexCount < newIndexCapacity>>1) + { + // Make capacity the previous power of two + while (indexCount < newIndexCapacity>>1) + newIndexCapacity >>= 1; + } + + if (newIndexCapacity != mIndexBufferCapacity) + { + mIndexBufferCapacity = newIndexCapacity; + // Create new index buffer + mRenderOp.indexData->indexBuffer = + HardwareBufferManager::getSingleton().createIndexBuffer( + HardwareIndexBuffer::IT_16BIT, + mIndexBufferCapacity, + HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY); // TODO: Custom HBU_? + } + + // Update index count in the render operation + mRenderOp.indexData->indexCount = indexCount; + } + } + + //------------------------------------------------------------------------------------------------ + Real DynamicRenderable::getBoundingRadius(void) const + { + return Math::Sqrt(std::max(mBox.getMaximum().squaredLength(), mBox.getMinimum().squaredLength())); + } + + //------------------------------------------------------------------------------------------------ + Real DynamicRenderable::getSquaredViewDepth(const Camera* cam) const + { + Vector3 vMin, vMax, vMid, vDist; + vMin = mBox.getMinimum(); + vMax = mBox.getMaximum(); + vMid = ((vMax - vMin) * 0.5) + vMin; + vDist = cam->getDerivedPosition() - vMid; + + return vDist.squaredLength(); + } + +/* + * ============================================================================================= + * BtOgre::DynamicLines + * ============================================================================================= + */ + + enum { + POSITION_BINDING, + TEXCOORD_BINDING + }; + + //------------------------------------------------------------------------------------------------ + DynamicLines::DynamicLines(OperationType opType) + { + initialize(opType,false); + setMaterial("BaseWhiteNoLighting"); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + DynamicLines::~DynamicLines() + { + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::setOperationType(OperationType opType) + { + mRenderOp.operationType = opType; + } + + //------------------------------------------------------------------------------------------------ + RenderOperation::OperationType DynamicLines::getOperationType() const + { + return mRenderOp.operationType; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::addPoint(const Vector3 &p) + { + mPoints.push_back(p); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::addPoint(Real x, Real y, Real z) + { + mPoints.push_back(Vector3(x,y,z)); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + const Vector3& DynamicLines::getPoint(unsigned short index) const + { + assert(index < mPoints.size() && "Point index is out of bounds!!"); + return mPoints[index]; + } + + //------------------------------------------------------------------------------------------------ + unsigned short DynamicLines::getNumPoints(void) const + { + return (unsigned short)mPoints.size(); + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::setPoint(unsigned short index, const Vector3 &value) + { + assert(index < mPoints.size() && "Point index is out of bounds!!"); + + mPoints[index] = value; + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::clear() + { + mPoints.clear(); + mDirty = true; + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::update() + { + if (mDirty) fillHardwareBuffers(); + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::createVertexDeclaration() + { + VertexDeclaration *decl = mRenderOp.vertexData->vertexDeclaration; + decl->addElement(POSITION_BINDING, 0, VET_FLOAT3, VES_POSITION); + } + + //------------------------------------------------------------------------------------------------ + void DynamicLines::fillHardwareBuffers() + { + int size = mPoints.size(); + + prepareHardwareBuffers(size,0); + + if (!size) { + mBox.setExtents(Vector3::ZERO,Vector3::ZERO); + mDirty=false; + return; + } + + Vector3 vaabMin = mPoints[0]; + Vector3 vaabMax = mPoints[0]; + + HardwareVertexBufferSharedPtr vbuf = + mRenderOp.vertexData->vertexBufferBinding->getBuffer(0); + + Real *prPos = static_cast(vbuf->lock(HardwareBuffer::HBL_DISCARD)); + { + for(int i = 0; i < size; i++) + { + *prPos++ = mPoints[i].x; + *prPos++ = mPoints[i].y; + *prPos++ = mPoints[i].z; + + if(mPoints[i].x < vaabMin.x) + vaabMin.x = mPoints[i].x; + if(mPoints[i].y < vaabMin.y) + vaabMin.y = mPoints[i].y; + if(mPoints[i].z < vaabMin.z) + vaabMin.z = mPoints[i].z; + + if(mPoints[i].x > vaabMax.x) + vaabMax.x = mPoints[i].x; + if(mPoints[i].y > vaabMax.y) + vaabMax.y = mPoints[i].y; + if(mPoints[i].z > vaabMax.z) + vaabMax.z = mPoints[i].z; + } + } + vbuf->unlock(); + + mBox.setExtents(vaabMin, vaabMax); + + mDirty = false; + } +} diff --git a/libs/openengine/bullet/BtOgreExtras.h b/libs/openengine/bullet/BtOgreExtras.h new file mode 100644 index 000000000..f3e1aa87a --- /dev/null +++ b/libs/openengine/bullet/BtOgreExtras.h @@ -0,0 +1,280 @@ +/* + * ===================================================================================== + * + * Filename: BtOgreExtras.h + * + * Description: Contains the Ogre Mesh to Bullet Shape converters. + * + * Version: 1.0 + * Created: 27/12/2008 01:45:56 PM + * + * Author: Nikhilesh (nikki) + * + * ===================================================================================== + */ + +#ifndef _BtOgreShapes_H_ +#define _BtOgreShapes_H_ + +#include "btBulletDynamicsCommon.h" +#include "OgreSimpleRenderable.h" +#include "OgreCamera.h" +#include "OgreHardwareBufferManager.h" +#include "OgreMaterialManager.h" +#include "OgreTechnique.h" +#include "OgrePass.h" + +#include "OgreLogManager.h" + +namespace BtOgre +{ + +typedef std::vector Vector3Array; + +//Converts from and to Bullet and Ogre stuff. Pretty self-explanatory. +class Convert +{ +public: + Convert() {}; + ~Convert() {}; + + static btQuaternion toBullet(const Ogre::Quaternion &q) + { + return btQuaternion(q.x, q.y, q.z, q.w); + } + static btVector3 toBullet(const Ogre::Vector3 &v) + { + return btVector3(v.x, v.y, v.z); + } + + static Ogre::Quaternion toOgre(const btQuaternion &q) + { + return Ogre::Quaternion(q.w(), q.x(), q.y(), q.z()); + } + static Ogre::Vector3 toOgre(const btVector3 &v) + { + return Ogre::Vector3(v.x(), v.y(), v.z()); + } +}; + +//From here on its debug-drawing stuff. ------------------------------------------------------------------ + +class DynamicRenderable : public Ogre::SimpleRenderable +{ +public: + /// Constructor + DynamicRenderable(); + /// Virtual destructor + virtual ~DynamicRenderable(); + + /** Initializes the dynamic renderable. + @remarks + This function should only be called once. It initializes the + render operation, and calls the abstract function + createVertexDeclaration(). + @param operationType The type of render operation to perform. + @param useIndices Specifies whether to use indices to determine the + vertices to use as input. */ + void initialize(Ogre::RenderOperation::OperationType operationType, + bool useIndices); + + /// Implementation of Ogre::SimpleRenderable + virtual Ogre::Real getBoundingRadius(void) const; + /// Implementation of Ogre::SimpleRenderable + virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const; + +protected: + /// Maximum capacity of the currently allocated vertex buffer. + size_t mVertexBufferCapacity; + /// Maximum capacity of the currently allocated index buffer. + size_t mIndexBufferCapacity; + + /** Creates the vertex declaration. + @remarks + Override and set mRenderOp.vertexData->vertexDeclaration here. + mRenderOp.vertexData will be created for you before this method + is called. */ + virtual void createVertexDeclaration() = 0; + + /** Prepares the hardware buffers for the requested vertex and index counts. + @remarks + This function must be called before locking the buffers in + fillHardwareBuffers(). It guarantees that the hardware buffers + are large enough to hold at least the requested number of + vertices and indices (if using indices). The buffers are + possibly reallocated to achieve this. + @par + The vertex and index count in the render operation are set to + the values of vertexCount and indexCount respectively. + @param vertexCount The number of vertices the buffer must hold. + + @param indexCount The number of indices the buffer must hold. This + parameter is ignored if not using indices. */ + void prepareHardwareBuffers(size_t vertexCount, size_t indexCount); + + /** Fills the hardware vertex and index buffers with data. + @remarks + This function must call prepareHardwareBuffers() before locking + the buffers to ensure the they are large enough for the data to + be written. Afterwards the vertex and index buffers (if using + indices) can be locked, and data can be written to them. */ + virtual void fillHardwareBuffers() = 0; +}; + +class DynamicLines : public DynamicRenderable +{ + typedef Ogre::Vector3 Vector3; + typedef Ogre::Quaternion Quaternion; + typedef Ogre::Camera Camera; + typedef Ogre::Real Real; + typedef Ogre::RenderOperation::OperationType OperationType; + +public: + /// Constructor - see setOperationType() for description of argument. + DynamicLines(OperationType opType=Ogre::RenderOperation::OT_LINE_STRIP); + virtual ~DynamicLines(); + + /// Add a point to the point list + void addPoint(const Ogre::Vector3 &p); + /// Add a point to the point list + void addPoint(Real x, Real y, Real z); + + /// Change the location of an existing point in the point list + void setPoint(unsigned short index, const Vector3 &value); + + /// Return the location of an existing point in the point list + const Vector3& getPoint(unsigned short index) const; + + /// Return the total number of points in the point list + unsigned short getNumPoints(void) const; + + /// Remove all points from the point list + void clear(); + + /// Call this to update the hardware buffer after making changes. + void update(); + + /** Set the type of operation to draw with. + * @param opType Can be one of + * - RenderOperation::OT_LINE_STRIP + * - RenderOperation::OT_LINE_LIST + * - RenderOperation::OT_POINT_LIST + * - RenderOperation::OT_TRIANGLE_LIST + * - RenderOperation::OT_TRIANGLE_STRIP + * - RenderOperation::OT_TRIANGLE_FAN + * The default is OT_LINE_STRIP. + */ + void setOperationType(OperationType opType); + OperationType getOperationType() const; + +protected: + /// Implementation DynamicRenderable, creates a simple vertex-only decl + virtual void createVertexDeclaration(); + /// Implementation DynamicRenderable, pushes point list out to hardware memory + virtual void fillHardwareBuffers(); + +private: + std::vector mPoints; + bool mDirty; +}; + +class DebugDrawer : public btIDebugDraw +{ +protected: + Ogre::SceneNode *mNode; + btDynamicsWorld *mWorld; + DynamicLines *mLineDrawer; + bool mDebugOn; + +public: + + DebugDrawer(Ogre::SceneNode *node, btDynamicsWorld *world) + : mNode(node), + mWorld(world), + mDebugOn(true) + { + mLineDrawer = new DynamicLines(Ogre::RenderOperation::OT_LINE_LIST); + mNode->attachObject(mLineDrawer); + + if (!Ogre::ResourceGroupManager::getSingleton().resourceGroupExists("BtOgre")) + Ogre::ResourceGroupManager::getSingleton().createResourceGroup("BtOgre"); + if (!Ogre::MaterialManager::getSingleton().resourceExists("BtOgre/DebugLines")) + { + Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().create("BtOgre/DebugLines", "BtOgre"); + mat->setReceiveShadows(false); + mat->setSelfIllumination(1,1,1); + } + + mLineDrawer->setMaterial("BtOgre/DebugLines"); + } + + ~DebugDrawer() + { + Ogre::MaterialManager::getSingleton().remove("BtOgre/DebugLines"); + Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup("BtOgre"); + delete mLineDrawer; + } + + void step() + { + if (mDebugOn) + { + mWorld->debugDrawWorld(); + mLineDrawer->update(); + mNode->needUpdate(); + mLineDrawer->clear(); + } + else + { + mLineDrawer->clear(); + mLineDrawer->update(); + mNode->needUpdate(); + } + } + + void drawLine(const btVector3& from,const btVector3& to,const btVector3& color) + { + mLineDrawer->addPoint(Convert::toOgre(from)); + mLineDrawer->addPoint(Convert::toOgre(to)); + } + + void drawContactPoint(const btVector3& PointOnB,const btVector3& normalOnB,btScalar distance,int lifeTime,const btVector3& color) + { + mLineDrawer->addPoint(Convert::toOgre(PointOnB)); + mLineDrawer->addPoint(Convert::toOgre(PointOnB) + (Convert::toOgre(normalOnB) * distance * 20)); + } + + void reportErrorWarning(const char* warningString) + { + Ogre::LogManager::getSingleton().logMessage(warningString); + } + + void draw3dText(const btVector3& location,const char* textString) + { + } + + //0 for off, anything else for on. + void setDebugMode(int isOn) + { + mDebugOn = (isOn == 0) ? false : true; + + if (!mDebugOn) + mLineDrawer->clear(); + } + + //0 for off, anything else for on. + int getDebugMode() const + { + return mDebugOn; + } + +}; + +} + +#endif + + + + + diff --git a/libs/openengine/bullet/BtOgreGP.h b/libs/openengine/bullet/BtOgreGP.h new file mode 100644 index 000000000..f0534de4b --- /dev/null +++ b/libs/openengine/bullet/BtOgreGP.h @@ -0,0 +1,142 @@ +/* + * ===================================================================================== + * + * Filename: BtOgreGP.h + * + * Description: The part of BtOgre that handles information transfer from Ogre to + * Bullet (like mesh data for making trimeshes). + * + * Version: 1.0 + * Created: 27/12/2008 03:29:56 AM + * + * Author: Nikhilesh (nikki) + * + * ===================================================================================== + */ + +#ifndef _BtOgrePG_H_ +#define _BtOgrePG_H_ + +#include "btBulletDynamicsCommon.h" +#include "BtOgreExtras.h" +#include "Ogre.h" + +namespace BtOgre { + +typedef std::map BoneIndex; +typedef std::pair BoneKeyIndex; + +class VertexIndexToShape +{ +public: + VertexIndexToShape(const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + ~VertexIndexToShape(); + + Ogre::Real getRadius(); + Ogre::Vector3 getSize(); + + + btSphereShape* createSphere(); + btBoxShape* createBox(); + btBvhTriangleMeshShape* createTrimesh(); + btCylinderShape* createCylinder(); + btConvexHullShape* createConvex(); + + const Ogre::Vector3* getVertices(); + unsigned int getVertexCount(); + const unsigned int* getIndices(); + unsigned int getIndexCount(); + +protected: + + void addStaticVertexData(const Ogre::VertexData *vertex_data); + + void addAnimatedVertexData(const Ogre::VertexData *vertex_data, + const Ogre::VertexData *blended_data, + const Ogre::Mesh::IndexMap *indexMap); + + void addIndexData(Ogre::IndexData *data, const unsigned int offset = 0); + + +protected: + Ogre::Vector3* mVertexBuffer; + unsigned int* mIndexBuffer; + unsigned int mVertexCount; + unsigned int mIndexCount; + + Ogre::Matrix4 mTransform; + + Ogre::Real mBoundRadius; + Ogre::Vector3 mBounds; + + BoneIndex *mBoneIndex; + + Ogre::Vector3 mScale; +}; + +//For static (non-animated) meshes. +class StaticMeshToShapeConverter : public VertexIndexToShape +{ +public: + + StaticMeshToShapeConverter(Ogre::Renderable *rend, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + StaticMeshToShapeConverter(Ogre::Entity *entity, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + StaticMeshToShapeConverter(); + + ~StaticMeshToShapeConverter(); + + void addEntity(Ogre::Entity *entity,const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + + void addMesh(const Ogre::MeshPtr &mesh, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + + +protected: + + Ogre::Entity* mEntity; + Ogre::SceneNode* mNode; +}; + +//For animated meshes. +class AnimatedMeshToShapeConverter : public VertexIndexToShape +{ +public: + + AnimatedMeshToShapeConverter(Ogre::Entity *entity, const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + AnimatedMeshToShapeConverter(); + ~AnimatedMeshToShapeConverter(); + + void addEntity(Ogre::Entity *entity,const Ogre::Matrix4 &transform = Ogre::Matrix4::IDENTITY); + void addMesh(const Ogre::MeshPtr &mesh, const Ogre::Matrix4 &transform); + + btBoxShape* createAlignedBox(unsigned char bone, + const Ogre::Vector3 &bonePosition, + const Ogre::Quaternion &boneOrientation); + + btBoxShape* createOrientedBox(unsigned char bone, + const Ogre::Vector3 &bonePosition, + const Ogre::Quaternion &boneOrientation); + +protected: + + bool getBoneVertices(unsigned char bone, + unsigned int &vertex_count, + Ogre::Vector3* &vertices, + const Ogre::Vector3 &bonePosition); + + bool getOrientedBox(unsigned char bone, + const Ogre::Vector3 &bonePosition, + const Ogre::Quaternion &boneOrientation, + Ogre::Vector3 &extents, + Ogre::Vector3 *axis, + Ogre::Vector3 ¢er); + + Ogre::Entity* mEntity; + Ogre::SceneNode* mNode; + + Ogre::Vector3 *mTransformedVerticesTemp; + size_t mTransformedVerticesTempSize; +}; + +} + +#endif diff --git a/libs/openengine/bullet/BtOgrePG.h b/libs/openengine/bullet/BtOgrePG.h new file mode 100644 index 000000000..9ff069a8f --- /dev/null +++ b/libs/openengine/bullet/BtOgrePG.h @@ -0,0 +1,81 @@ +/* + * ===================================================================================== + * + * Filename: BtOgrePG.h + * + * Description: The part of BtOgre that handles information transfer from Bullet to + * Ogre (like updating graphics object positions). + * + * Version: 1.0 + * Created: 27/12/2008 03:40:56 AM + * + * Author: Nikhilesh (nikki) + * + * ===================================================================================== + */ + +#ifndef _BtOgreGP_H_ +#define _BtOgreGP_H_ + +#include "btBulletDynamicsCommon.h" +#include "OgreSceneNode.h" +#include "BtOgreExtras.h" + +namespace BtOgre { + +//A MotionState is Bullet's way of informing you about updates to an object. +//Pass this MotionState to a btRigidBody to have your SceneNode updated automaticaly. +class RigidBodyState : public btMotionState +{ + protected: + btTransform mTransform; + btTransform mCenterOfMassOffset; + + Ogre::SceneNode *mNode; + + public: + RigidBodyState(Ogre::SceneNode *node, const btTransform &transform, const btTransform &offset = btTransform::getIdentity()) + : mTransform(transform), + mCenterOfMassOffset(offset), + mNode(node) + { + } + + RigidBodyState(Ogre::SceneNode *node) + : mTransform(((node != NULL) ? BtOgre::Convert::toBullet(node->getOrientation()) : btQuaternion(0,0,0,1)), + ((node != NULL) ? BtOgre::Convert::toBullet(node->getPosition()) : btVector3(0,0,0))), + mCenterOfMassOffset(btTransform::getIdentity()), + mNode(node) + { + } + + virtual void getWorldTransform(btTransform &ret) const + { + ret = mCenterOfMassOffset.inverse() * mTransform; + } + + virtual void setWorldTransform(const btTransform &in) + { + if (mNode == NULL) + return; + + mTransform = in; + btTransform transform = in * mCenterOfMassOffset; + + btQuaternion rot = transform.getRotation(); + btVector3 pos = transform.getOrigin(); + mNode->setOrientation(rot.w(), rot.x(), rot.y(), rot.z()); + mNode->setPosition(pos.x(), pos.y(), pos.z()); + } + + void setNode(Ogre::SceneNode *node) + { + mNode = node; + } +}; + +//Softbody-Ogre connection goes here! + +} + +#endif diff --git a/libs/openengine/bullet/BulletShapeLoader.cpp b/libs/openengine/bullet/BulletShapeLoader.cpp new file mode 100644 index 000000000..48b87e1e8 --- /dev/null +++ b/libs/openengine/bullet/BulletShapeLoader.cpp @@ -0,0 +1,124 @@ +#include "BulletShapeLoader.h" + + + +BulletShape::BulletShape(Ogre::ResourceManager* creator, const Ogre::String &name, + Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual, + Ogre::ManualResourceLoader *loader) : +Ogre::Resource(creator, name, handle, group, isManual, loader) +{ + /* If you were storing a pointer to an object, then you would set that pointer to NULL here. + */ + + /* For consistency with StringInterface, but we don't add any parameters here + That's because the Resource implementation of StringInterface is to + list all the options that need to be set before loading, of which + we have none as such. Full details can be set through scripts. + */ + Shape = NULL; + collide = true; + createParamDictionary("BulletShape"); +} + +BulletShape::~BulletShape() +{ +} + +// farm out to BulletShapeLoader +void BulletShape::loadImpl() +{ + mLoader->loadResource(this); +} + +void BulletShape::deleteShape(btCollisionShape* mShape) +{ + if(mShape!=NULL) + { + if(mShape->isCompound()) + { + btCompoundShape* ms = static_cast(Shape); + int a = ms->getNumChildShapes(); + for(int i=0; i getChildShape(i)); + } + } + delete mShape; + } + mShape = NULL; +} + +void BulletShape::unloadImpl() +{ + deleteShape(Shape); +} + +//TODO:change this? +size_t BulletShape::calculateSize() const +{ + return 1; +} + + + +//============================================================================================================= +template<> BulletShapeManager *Ogre::Singleton::ms_Singleton = 0; + +BulletShapeManager *BulletShapeManager::getSingletonPtr() +{ + return ms_Singleton; +} + +BulletShapeManager &BulletShapeManager::getSingleton() +{ + assert(ms_Singleton); + return(*ms_Singleton); +} + +BulletShapeManager::BulletShapeManager() +{ + mResourceType = "BulletShape"; + + // low, because it will likely reference other resources + mLoadOrder = 30.0f; + + // this is how we register the ResourceManager with OGRE + Ogre::ResourceGroupManager::getSingleton()._registerResourceManager(mResourceType, this); +} + +BulletShapeManager::~BulletShapeManager() +{ + // and this is how we unregister it + Ogre::ResourceGroupManager::getSingleton()._unregisterResourceManager(mResourceType); +} + +BulletShapePtr BulletShapeManager::load(const Ogre::String &name, const Ogre::String &group) +{ + BulletShapePtr textf = getByName(name); + + if (textf.isNull()) + textf = create(name, group); + + textf->load(); + return textf; +} + +Ogre::Resource *BulletShapeManager::createImpl(const Ogre::String &name, Ogre::ResourceHandle handle, + const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader, + const Ogre::NameValuePairList *createParams) +{ + BulletShape* res = new BulletShape(this, name, handle, group, isManual, loader); + //if(isManual) + //{ + //loader->loadResource(res); + //} + return res; +} + + +//==================================================================== +void BulletShapeLoader::loadResource(Ogre::Resource *resource) +{} + +void BulletShapeLoader::load(const std::string &name,const std::string &group) +{} diff --git a/libs/openengine/bullet/BulletShapeLoader.h b/libs/openengine/bullet/BulletShapeLoader.h new file mode 100644 index 000000000..316ee523c --- /dev/null +++ b/libs/openengine/bullet/BulletShapeLoader.h @@ -0,0 +1,138 @@ +#ifndef _BULLET_SHAPE_LOADER_H_ +#define _BULLET_SHAPE_LOADER_H_ + +#include +#include +#include + +//For some reason, Ogre Singleton cannot be used in another namespace, that's why there is no namespace here. +//But the risk of name collision seems pretty low here. + +/** +*Define a new resource which describe a Shape usable by bullet.See BulletShapeManager for how to get/use them. +*/ +class BulletShape : public Ogre::Resource +{ + Ogre::String mString; + +protected: + void loadImpl(); + void unloadImpl(); + size_t calculateSize() const; + + void deleteShape(btCollisionShape* mShape); + +public: + + BulletShape(Ogre::ResourceManager *creator, const Ogre::String &name, + Ogre::ResourceHandle handle, const Ogre::String &group, bool isManual = false, + Ogre::ManualResourceLoader *loader = 0); + + virtual ~BulletShape(); + + btCollisionShape* Shape; + //this flag indicate if the shape is used for collision or if it's for raycasting only. + bool collide; +}; + +/** +* +*/ +class BulletShapePtr : public Ogre::SharedPtr +{ +public: + BulletShapePtr() : Ogre::SharedPtr() {} + explicit BulletShapePtr(BulletShape *rep) : Ogre::SharedPtr(rep) {} + BulletShapePtr(const BulletShapePtr &r) : Ogre::SharedPtr(r) {} + BulletShapePtr(const Ogre::ResourcePtr &r) : Ogre::SharedPtr() + { + if( r.isNull() ) + return; + // lock & copy other mutex pointer + OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME) + OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME) + pRep = static_cast(r.getPointer()); + pUseCount = r.useCountPointer(); + useFreeMethod = r.freeMethod(); + if (pUseCount) + { + ++(*pUseCount); + } + } + + /// Operator used to convert a ResourcePtr to a BulletShapePtr + BulletShapePtr& operator=(const Ogre::ResourcePtr& r) + { + if(pRep == static_cast(r.getPointer())) + return *this; + release(); + if( r.isNull() ) + return *this; // resource ptr is null, so the call to release above has done all we need to do. + // lock & copy other mutex pointer + OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME) + OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME) + pRep = static_cast(r.getPointer()); + pUseCount = r.useCountPointer(); + useFreeMethod = r.freeMethod(); + if (pUseCount) + { + ++(*pUseCount); + } + return *this; + } +}; + + + + +/** +*Hold any BulletShape that was created by the ManualBulletShapeLoader. +* +*To get a bulletShape, you must load it first. +*First, create a manualBulletShapeLoader. Then call ManualBulletShapeManager->load(). This create an "empty" resource. +*Then use BulletShapeManager->load(). This will fill the resource with the required info. +*To get the resource,use BulletShapeManager::getByName. +*When you use the resource no more, just use BulletShapeManager->unload(). It won't completly delete the resource, but it will +*"empty" it.This allow a better management of memory: when you are leaving a cell, just unload every useless shape. +* +*Alternatively, you can call BulletShape->load() in order to actually load the resource. +*When you are finished with it, just call BulletShape->unload(). +* +*IMO: prefere the first methode, i am not completly sure about the 2nd. +* +*Important Note: i have no idea of what happen if you try to load two time the same resource without unloading. +*It won't crash, but it might lead to memory leaks(I don't know how Ogre handle this). So don't do it! +*/ +class BulletShapeManager : public Ogre::ResourceManager, public Ogre::Singleton +{ +protected: + + // must implement this from ResourceManager's interface + Ogre::Resource *createImpl(const Ogre::String &name, Ogre::ResourceHandle handle, + const Ogre::String &group, bool isManual, Ogre::ManualResourceLoader *loader, + const Ogre::NameValuePairList *createParams); + +public: + + BulletShapeManager(); + virtual ~BulletShapeManager(); + + virtual BulletShapePtr load(const Ogre::String &name, const Ogre::String &group); + + static BulletShapeManager &getSingleton(); + static BulletShapeManager *getSingletonPtr(); +}; + +class BulletShapeLoader : public Ogre::ManualResourceLoader +{ +public: + + BulletShapeLoader(){}; + virtual ~BulletShapeLoader() {} + + virtual void loadResource(Ogre::Resource *resource); + + virtual void load(const std::string &name,const std::string &group); +}; + +#endif diff --git a/libs/openengine/bullet/CMotionState.cpp b/libs/openengine/bullet/CMotionState.cpp new file mode 100644 index 000000000..5ddef5175 --- /dev/null +++ b/libs/openengine/bullet/CMotionState.cpp @@ -0,0 +1,45 @@ +#include "CMotionState.h" +#include "physic.hpp" + +#include +#include +#include +//#include + +namespace OEngine { +namespace Physic +{ + + CMotionState::CMotionState(PhysicEngine* eng,std::string name) + { + pEng = eng; + tr.setIdentity(); + pName = name; + }; + + void CMotionState::getWorldTransform(btTransform &worldTrans) const + { + worldTrans = tr; + } + + void CMotionState::setWorldTransform(const btTransform &worldTrans) + { + tr = worldTrans; + + PhysicEvent evt; + evt.isNPC = isNPC; + evt.isPC = isPC; + evt.newTransform = tr; + evt.RigidBodyName = pName; + + if(isPC) + { + pEng->PEventList.push_back(evt); + } + else + { + pEng->NPEventList.push_back(evt); + } + } + +}} diff --git a/libs/openengine/bullet/CMotionState.h b/libs/openengine/bullet/CMotionState.h new file mode 100644 index 000000000..3508ab4ef --- /dev/null +++ b/libs/openengine/bullet/CMotionState.h @@ -0,0 +1,52 @@ +#ifndef OENGINE_CMOTIONSTATE_H +#define OENGINE_CMOTIONSTATE_H + +#include +#include + +namespace OEngine { +namespace Physic +{ + class PhysicEngine; + + /** + * A CMotionState is associated with a single RigidBody. + * When the RigidBody is moved by bullet, bullet will call the function setWorldTransform. + * for more info, see the bullet Wiki at btMotionState. + */ + class CMotionState:public btMotionState + { + public: + + CMotionState(PhysicEngine* eng,std::string name); + + /** + * Return the position of the RigidBody. + */ + virtual void getWorldTransform(btTransform &worldTrans) const; + + /** + * Function called by bullet when the RigidBody is moved. + * It add an event to the EventList of the PhysicEngine class. + */ + virtual void setWorldTransform(const btTransform &worldTrans); + + protected: + PhysicEngine* pEng; + btTransform tr; + bool isNPC; + bool isPC; + + std::string pName; + }; + + struct PhysicEvent + { + bool isNPC; + bool isPC; + btTransform newTransform; + std::string RigidBodyName; + }; + +}} +#endif diff --git a/libs/openengine/bullet/btKinematicCharacterController.cpp b/libs/openengine/bullet/btKinematicCharacterController.cpp new file mode 100644 index 000000000..fc4f3278f --- /dev/null +++ b/libs/openengine/bullet/btKinematicCharacterController.cpp @@ -0,0 +1,643 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "LinearMath/btIDebugDraw.h" +#include "BulletCollision/CollisionDispatch/btGhostObject.h" +#include "BulletCollision/CollisionShapes/btMultiSphereShape.h" +#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" +#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" +#include "BulletCollision/CollisionDispatch/btCollisionWorld.h" +#include "LinearMath/btDefaultMotionState.h" +#include "btKinematicCharacterController.h" + +///@todo Interact with dynamic objects, +///Ride kinematicly animated platforms properly +///Support ducking +class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback +{ +public: + btKinematicClosestNotMeRayResultCallback (btCollisionObject* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + { + m_me[0] = me; + count = 1; + } + + btKinematicClosestNotMeRayResultCallback (btCollisionObject* me[], int count_) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + { + count = count_; + + for(int i = 0; i < count; i++) + m_me[i] = me[i]; + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) + { + for(int i = 0; i < count; i++) + if (rayResult.m_collisionObject == m_me[i]) + return 1.0; + + return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace); + } +protected: + btCollisionObject* m_me[10]; + int count; +}; + +class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback +{ +public: + btKinematicClosestNotMeConvexResultCallback( btCollisionObject* me, const btVector3& up, btScalar minSlopeDot ) + : btCollisionWorld::ClosestConvexResultCallback( btVector3( 0.0, 0.0, 0.0 ), btVector3( 0.0, 0.0, 0.0 ) ), + m_me( me ), m_up( up ), m_minSlopeDot( minSlopeDot ) + { + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) + { + if( convexResult.m_hitCollisionObject == m_me ) + return btScalar( 1 ); + + btVector3 hitNormalWorld; + if( normalInWorldSpace ) + { + hitNormalWorld = convexResult.m_hitNormalLocal; + } + else + { + ///need to transform normal into worldspace + hitNormalWorld = m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; + } + + // NOTE : m_hitNormalLocal is not always vertical on the ground with a capsule or a box... + + btScalar dotUp = m_up.dot(hitNormalWorld); + if( dotUp < m_minSlopeDot ) + return btScalar( 1 ); + + return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace); + } + +protected: + btCollisionObject* m_me; + const btVector3 m_up; + btScalar m_minSlopeDot; +}; + + + +btKinematicCharacterController::btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject_, + btPairCachingGhostObject* internalGhostObject_, + btScalar stepHeight, + btScalar constantScale, + btScalar gravity, + btScalar fallVelocity, + btScalar jumpVelocity, + btScalar recoveringFactor ) +{ + m_upAxis = btKinematicCharacterController::Y_AXIS; + + m_walkDirection.setValue( btScalar( 0 ), btScalar( 0 ), btScalar( 0 ) ); + + m_useGhostObjectSweepTest = true; + + externalGhostObject = externalGhostObject_; + internalGhostObject = internalGhostObject_; + + m_recoveringFactor = recoveringFactor; + + m_stepHeight = stepHeight; + + m_useWalkDirection = true; // use walk direction by default, legacy behavior + m_velocityTimeInterval = btScalar( 0 ); + m_verticalVelocity = btScalar( 0 ); + m_verticalOffset = btScalar( 0 ); + + m_gravity = constantScale * gravity; + m_fallSpeed = constantScale * fallVelocity; // Terminal velocity of a sky diver in m/s. + + m_jumpSpeed = constantScale * jumpVelocity; // ? + m_wasJumping = false; + + setMaxSlope( btRadians( 45.0 ) ); + + mCollision = true; +} + + +btKinematicCharacterController::~btKinematicCharacterController () +{ +} + +void btKinematicCharacterController::setVerticalVelocity(float z) +{ + m_verticalVelocity = z; +} + +bool btKinematicCharacterController::recoverFromPenetration( btCollisionWorld* collisionWorld ) +{ + bool penetration = false; + + if(!mCollision) return penetration; + + collisionWorld->getDispatcher()->dispatchAllCollisionPairs( internalGhostObject->getOverlappingPairCache(), + collisionWorld->getDispatchInfo(), + collisionWorld->getDispatcher() ); + + btVector3 currentPosition = internalGhostObject->getWorldTransform().getOrigin(); + + btScalar maxPen = btScalar( 0 ); + + for( int i = 0; i < internalGhostObject->getOverlappingPairCache()->getNumOverlappingPairs(); i++ ) + { + m_manifoldArray.resize(0); + + btBroadphasePair* collisionPair = &internalGhostObject->getOverlappingPairCache()->getOverlappingPairArray()[i]; + + if( collisionPair->m_algorithm ) + collisionPair->m_algorithm->getAllContactManifolds( m_manifoldArray ); + + + for( int j = 0; j < m_manifoldArray.size(); j++ ) + { + btPersistentManifold* manifold = m_manifoldArray[j]; + + btScalar directionSign = manifold->getBody0() == internalGhostObject ? btScalar( -1.0 ) : btScalar( 1.0 ); + + for( int p = 0; p < manifold->getNumContacts(); p++ ) + { + const btManifoldPoint&pt = manifold->getContactPoint( p ); + if( (manifold->getBody1() == externalGhostObject && manifold->getBody0() == internalGhostObject) + ||(manifold->getBody0() == externalGhostObject && manifold->getBody1() == internalGhostObject) ) + { + } + else + { + btScalar dist = pt.getDistance(); + + if( dist < 0.0 ) + { + if( dist < maxPen ) + maxPen = dist; + + // NOTE : btScalar affects the stairs but the parkinson... + // 0.0 , the capsule can break the walls... + currentPosition += pt.m_normalWorldOnB * directionSign * dist * m_recoveringFactor; + + penetration = true; + } + } + } + + // ??? + //manifold->clearManifold(); + } + } + + btTransform transform = internalGhostObject->getWorldTransform(); + + transform.setOrigin( currentPosition ); + + internalGhostObject->setWorldTransform( transform ); + externalGhostObject->setWorldTransform( transform ); + + return penetration; +} + + +btVector3 btKinematicCharacterController::stepUp( btCollisionWorld* world, const btVector3& currentPosition, btScalar& currentStepOffset ) +{ + btVector3 targetPosition = currentPosition + getUpAxisDirections()[ m_upAxis ] * ( m_stepHeight + ( m_verticalOffset > btScalar( 0.0 ) ? m_verticalOffset : 0.0 ) ); + + //if the no collisions mode is on, no need to go any further + if(!mCollision) + { + currentStepOffset = m_stepHeight; + return targetPosition; + } + + // Retrieve the collision shape + // + btCollisionShape* collisionShape = externalGhostObject->getCollisionShape(); + btAssert( collisionShape->isConvex() ); + + btConvexShape* convexShape = ( btConvexShape* )collisionShape; + + // FIXME: Handle penetration properly + // + btTransform start; + start.setIdentity(); + start.setOrigin( currentPosition + getUpAxisDirections()[ m_upAxis ] * ( convexShape->getMargin() ) ); + + btTransform end; + end.setIdentity(); + end.setOrigin( targetPosition ); + + btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, -getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine ); + callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; + + // Sweep test + // + if( m_useGhostObjectSweepTest ) + externalGhostObject->convexSweepTest( convexShape, start, end, callback, world->getDispatchInfo().m_allowedCcdPenetration ); + + else + world->convexSweepTest( convexShape, start, end, callback ); + + if( callback.hasHit() ) + { + // Only modify the position if the hit was a slope and not a wall or ceiling. + // + if( callback.m_hitNormalWorld.dot(getUpAxisDirections()[m_upAxis]) > btScalar( 0.0 ) ) + { + // We moved up only a fraction of the step height + // + currentStepOffset = m_stepHeight * callback.m_closestHitFraction; + + return currentPosition.lerp( targetPosition, callback.m_closestHitFraction ); + } + + m_verticalVelocity = btScalar( 0.0 ); + m_verticalOffset = btScalar( 0.0 ); + + return currentPosition; + } + else + { + currentStepOffset = m_stepHeight; + return targetPosition; + } +} + + +///Reflect the vector d around the vector r +inline btVector3 reflect( const btVector3& d, const btVector3& r ) +{ + return d - ( btScalar( 2.0 ) * d.dot( r ) ) * r; +} + + +///Project a vector u on another vector v +inline btVector3 project( const btVector3& u, const btVector3& v ) +{ + return v * u.dot( v ); +} + + +///Helper for computing the character sliding +inline btVector3 slide( const btVector3& direction, const btVector3& planeNormal ) +{ + return direction - project( direction, planeNormal ); +} + + + +btVector3 slideOnCollision( const btVector3& fromPosition, const btVector3& toPosition, const btVector3& hitNormal ) +{ + btVector3 moveDirection = toPosition - fromPosition; + btScalar moveLength = moveDirection.length(); + + if( moveLength <= btScalar( SIMD_EPSILON ) ) + return toPosition; + + moveDirection.normalize(); + + btVector3 reflectDir = reflect( moveDirection, hitNormal ); + reflectDir.normalize(); + + return fromPosition + slide( reflectDir, hitNormal ) * moveLength; +} + + +btVector3 btKinematicCharacterController::stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove ) +{ + // We go to ! + // + btVector3 targetPosition = currentPosition + walkMove; + + //if the no collisions mode is on, no need to go any further + if(!mCollision) return targetPosition; + + // Retrieve the collision shape + // + btCollisionShape* collisionShape = externalGhostObject->getCollisionShape(); + btAssert( collisionShape->isConvex() ); + + btConvexShape* convexShape = ( btConvexShape* )collisionShape; + + btTransform start; + start.setIdentity(); + + btTransform end; + end.setIdentity(); + + btScalar fraction = btScalar( 1.0 ); + + // This optimization scheme suffers in the corners. + // It basically jumps from a wall to another, then fails to find a new + // position (after 4 iterations here) and finally don't move at all. + // + // The stepping algorithm adds some problems with stairs. It seems + // the treads create some fake corner using capsules for collisions. + // + for( int i = 0; i < 4 && fraction > btScalar( 0.01 ); i++ ) + { + start.setOrigin( currentPosition ); + end.setOrigin( targetPosition ); + + btVector3 sweepDirNegative = currentPosition - targetPosition; + + btKinematicClosestNotMeConvexResultCallback callback( externalGhostObject, sweepDirNegative, btScalar( 0.0 ) ); + callback.m_collisionFilterGroup = externalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = externalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; + + if( m_useGhostObjectSweepTest ) + externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + else + collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + if( callback.hasHit() ) + { + // Try another target position + // + targetPosition = slideOnCollision( currentPosition, targetPosition, callback.m_hitNormalWorld ); + fraction = callback.m_closestHitFraction; + } + else + + // Move to the valid target position + // + return targetPosition; + } + + // Don't move if you can't find a valid target position... + // It prevents some flickering. + // + return currentPosition; +} + + +///Handle the gravity +btScalar btKinematicCharacterController::addFallOffset( bool wasOnGround, btScalar currentStepOffset, btScalar dt ) +{ + btScalar downVelocity = ( m_verticalVelocity < 0.0 ? -m_verticalVelocity : btScalar( 0.0 ) ) * dt; + + if( downVelocity > btScalar( 0.0 ) && downVelocity < m_stepHeight && ( wasOnGround || !m_wasJumping ) ) + downVelocity = m_stepHeight; + + return currentStepOffset + downVelocity; +} + + +btVector3 btKinematicCharacterController::stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset ) +{ + btVector3 stepDrop = getUpAxisDirections()[ m_upAxis ] * currentStepOffset; + + // Be sure we are falling from the last m_currentPosition + // It prevents some flickering + // + btVector3 targetPosition = currentPosition - stepDrop; + + //if the no collisions mode is on, no need to go any further + if(!mCollision) return targetPosition; + + btTransform start; + start.setIdentity(); + start.setOrigin( currentPosition ); + + btTransform end; + end.setIdentity(); + end.setOrigin( targetPosition ); + + btKinematicClosestNotMeConvexResultCallback callback( internalGhostObject, getUpAxisDirections()[ m_upAxis ], m_maxSlopeCosine ); + callback.m_collisionFilterGroup = internalGhostObject->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = internalGhostObject->getBroadphaseHandle()->m_collisionFilterMask; + + // Retrieve the collision shape + // + btCollisionShape* collisionShape = internalGhostObject->getCollisionShape(); + btAssert( collisionShape->isConvex() ); + btConvexShape* convexShape = ( btConvexShape* )collisionShape; + + if( m_useGhostObjectSweepTest ) + externalGhostObject->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + else + collisionWorld->convexSweepTest( convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration ); + + if( callback.hasHit() ) + { + m_verticalVelocity = btScalar( 0.0 ); + m_verticalOffset = btScalar( 0.0 ); + m_wasJumping = false; + + // We dropped a fraction of the height -> hit floor + // + return currentPosition.lerp( targetPosition, callback.m_closestHitFraction ); + } + else + + // We dropped the full height + // + return targetPosition; +} + + + +void btKinematicCharacterController::setWalkDirection( const btVector3& walkDirection ) +{ + m_useWalkDirection = true; + m_walkDirection = walkDirection; +} + + +void btKinematicCharacterController::setVelocityForTimeInterval( const btVector3& velocity, btScalar timeInterval ) +{ + m_useWalkDirection = false; + m_walkDirection = velocity; + m_velocityTimeInterval = timeInterval; +} + + +void btKinematicCharacterController::reset() +{ +} + + +void btKinematicCharacterController::warp( const btVector3& origin ) +{ + btTransform transform; + transform.setIdentity(); + transform.setOrigin( -origin ); + + externalGhostObject->setWorldTransform( transform ); + internalGhostObject->setWorldTransform( transform ); +} + + +void btKinematicCharacterController::preStep( btCollisionWorld* collisionWorld ) +{ + BT_PROFILE( "preStep" ); + + for( int i = 0; i < 4 && recoverFromPenetration ( collisionWorld ); i++ ); +} + + +void btKinematicCharacterController::playerStep( btCollisionWorld* collisionWorld, btScalar dt ) +{ + BT_PROFILE( "playerStep" ); + + if( !m_useWalkDirection && m_velocityTimeInterval <= btScalar( 0.0 ) ) + return; + + bool wasOnGround = onGround(); + + // Handle the gravity + // + m_verticalVelocity -= m_gravity * dt; + + if( m_verticalVelocity > 0.0 && m_verticalVelocity > m_jumpSpeed ) + m_verticalVelocity = m_jumpSpeed; + + if( m_verticalVelocity < 0.0 && btFabs( m_verticalVelocity ) > btFabs( m_fallSpeed ) ) + m_verticalVelocity = -btFabs( m_fallSpeed ); + + m_verticalOffset = m_verticalVelocity * dt; + + // This forced stepping up can cause problems when the character + // walks (jump in fact...) under too low ceilings. + // + btVector3 currentPosition = externalGhostObject->getWorldTransform().getOrigin(); + btScalar currentStepOffset; + + currentPosition = stepUp( collisionWorld, currentPosition, currentStepOffset ); + + // Move in the air and slide against the walls ignoring the stair steps. + // + if( m_useWalkDirection ) + currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, m_walkDirection ); + + else + { + btScalar dtMoving = ( dt < m_velocityTimeInterval ) ? dt : m_velocityTimeInterval; + m_velocityTimeInterval -= dt; + + // How far will we move while we are moving ? + // + btVector3 moveDirection = m_walkDirection * dtMoving; + + currentPosition = stepForwardAndStrafe( collisionWorld, currentPosition, moveDirection ); + } + + // Finally find the ground. + // + currentStepOffset = addFallOffset( wasOnGround, currentStepOffset, dt ); + + currentPosition = stepDown( collisionWorld, currentPosition, currentStepOffset ); + + // Apply the new position to the collision objects. + // + btTransform tranform; + tranform = externalGhostObject->getWorldTransform(); + tranform.setOrigin( currentPosition ); + + externalGhostObject->setWorldTransform( tranform ); + internalGhostObject->setWorldTransform( tranform ); +} + + +void btKinematicCharacterController::setFallSpeed( btScalar fallSpeed ) +{ + m_fallSpeed = fallSpeed; +} + + +void btKinematicCharacterController::setJumpSpeed( btScalar jumpSpeed ) +{ + m_jumpSpeed = jumpSpeed; +} + + +void btKinematicCharacterController::setMaxJumpHeight( btScalar maxJumpHeight ) +{ + m_maxJumpHeight = maxJumpHeight; +} + + +bool btKinematicCharacterController::canJump() const +{ + return onGround(); +} + + +void btKinematicCharacterController::jump() +{ + if( !canJump() ) + return; + + m_verticalVelocity = m_jumpSpeed; + m_wasJumping = true; +} + + +void btKinematicCharacterController::setGravity( btScalar gravity ) +{ + m_gravity = gravity; +} + + +btScalar btKinematicCharacterController::getGravity() const +{ + return m_gravity; +} + + +void btKinematicCharacterController::setMaxSlope( btScalar slopeRadians ) +{ + m_maxSlopeRadians = slopeRadians; + m_maxSlopeCosine = btCos( slopeRadians ); +} + + +btScalar btKinematicCharacterController::getMaxSlope() const +{ + return m_maxSlopeRadians; +} + + +bool btKinematicCharacterController::onGround() const +{ + return btFabs( m_verticalVelocity ) < btScalar( SIMD_EPSILON ) && + btFabs( m_verticalOffset ) < btScalar( SIMD_EPSILON ); +} + + +btVector3* btKinematicCharacterController::getUpAxisDirections() +{ + static btVector3 sUpAxisDirection[] = + { + btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 0.0 ) ), + btVector3( btScalar( 0.0 ), btScalar( 1.0 ), btScalar( 0.0 ) ), + btVector3( btScalar( 0.0 ), btScalar( 0.0 ), btScalar( 1.0 ) ) + }; + + return sUpAxisDirection; +} + + +void btKinematicCharacterController::debugDraw( btIDebugDraw* debugDrawer ) +{ +} diff --git a/libs/openengine/bullet/btKinematicCharacterController.h b/libs/openengine/bullet/btKinematicCharacterController.h new file mode 100644 index 000000000..d24cd9722 --- /dev/null +++ b/libs/openengine/bullet/btKinematicCharacterController.h @@ -0,0 +1,168 @@ +/* +Bullet Continuous Collision Detection and Physics Library +Copyright (c) 2003-2008 Erwin Coumans http://bulletphysics.com + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef KINEMATIC_CHARACTER_CONTROLLER_H +#define KINEMATIC_CHARACTER_CONTROLLER_H + +#include "LinearMath/btVector3.h" +#include "LinearMath/btQuickprof.h" + +#include "BulletDynamics/Character/btCharacterControllerInterface.h" + +#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h" + + +class btCollisionShape; +class btRigidBody; +class btCollisionWorld; +class btCollisionDispatcher; +class btPairCachingGhostObject; + +///btKinematicCharacterController is an object that supports a sliding motion in a world. +///It uses a ghost object and convex sweep test to test for upcoming collisions. This is combined with discrete collision detection to recover from penetrations. +///Interaction between btKinematicCharacterController and dynamic rigid bodies needs to be explicity implemented by the user. +class btKinematicCharacterController : public btCharacterControllerInterface +{ +public: + enum UpAxis + { + X_AXIS = 0, + Y_AXIS = 1, + Z_AXIS = 2 + }; + +private: + btPairCachingGhostObject* externalGhostObject; // use this for querying collisions for sliding and move + btPairCachingGhostObject* internalGhostObject; // and this for recoreving from penetrations + + btScalar m_verticalVelocity; + btScalar m_verticalOffset; + btScalar m_fallSpeed; + btScalar m_jumpSpeed; + btScalar m_maxJumpHeight; + btScalar m_maxSlopeRadians; // Slope angle that is set (used for returning the exact value) + btScalar m_maxSlopeCosine; // Cosine equivalent of m_maxSlopeRadians (calculated once when set, for optimization) + btScalar m_gravity; + btScalar m_recoveringFactor; + + btScalar m_stepHeight; + + ///this is the desired walk direction, set by the user + btVector3 m_walkDirection; + + ///keep track of the contact manifolds + btManifoldArray m_manifoldArray; + + ///Gravity attributes + bool m_wasJumping; + + bool m_useGhostObjectSweepTest; + bool m_useWalkDirection; + btScalar m_velocityTimeInterval; + + UpAxis m_upAxis; + + static btVector3* getUpAxisDirections(); + + bool recoverFromPenetration ( btCollisionWorld* collisionWorld ); + + btVector3 stepUp( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar& currentStepOffset ); + btVector3 stepForwardAndStrafe( btCollisionWorld* collisionWorld, const btVector3& currentPosition, const btVector3& walkMove ); + btScalar addFallOffset( bool wasJumping, btScalar currentStepOffset, btScalar dt ); + btVector3 stepDown( btCollisionWorld* collisionWorld, const btVector3& currentPosition, btScalar currentStepOffset ); + +public: + /// externalGhostObject is used for querying the collisions for sliding along the wall, + /// and internalGhostObject is used for querying the collisions for recovering from large penetrations. + /// These parameters can point on the same object. + /// Using a smaller internalGhostObject can help for removing some flickering but create some + /// stopping artefacts when sliding along stairs or small walls. + /// Don't forget to scale gravity and fallSpeed if you scale the world. + btKinematicCharacterController( btPairCachingGhostObject* externalGhostObject, + btPairCachingGhostObject* internalGhostObject, + btScalar stepHeight, + btScalar constantScale = btScalar( 1.0 ), + btScalar gravity = btScalar( 9.8 ), + btScalar fallVelocity = btScalar( 55.0 ), + btScalar jumpVelocity = btScalar( 9.8 ), + btScalar recoveringFactor = btScalar( 0.2 ) ); + + ~btKinematicCharacterController (); + + void setVerticalVelocity(float z); + + ///btActionInterface interface + virtual void updateAction( btCollisionWorld* collisionWorld, btScalar deltaTime ) + { + preStep( collisionWorld ); + playerStep( collisionWorld, deltaTime ); + } + + ///btActionInterface interface + void debugDraw( btIDebugDraw* debugDrawer ); + + void setUpAxis( UpAxis axis ) + { + m_upAxis = axis; + } + + /// This should probably be called setPositionIncrementPerSimulatorStep. + /// This is neither a direction nor a velocity, but the amount to + /// increment the position each simulation iteration, regardless + /// of dt. + /// This call will reset any velocity set by setVelocityForTimeInterval(). + virtual void setWalkDirection(const btVector3& walkDirection); + + /// Caller provides a velocity with which the character should move for + /// the given time period. After the time period, velocity is reset + /// to zero. + /// This call will reset any walk direction set by setWalkDirection(). + /// Negative time intervals will result in no motion. + virtual void setVelocityForTimeInterval(const btVector3& velocity, + btScalar timeInterval); + + void reset(); + void warp( const btVector3& origin ); + + void preStep( btCollisionWorld* collisionWorld ); + void playerStep( btCollisionWorld* collisionWorld, btScalar dt ); + + void setFallSpeed( btScalar fallSpeed ); + void setJumpSpeed( btScalar jumpSpeed ); + void setMaxJumpHeight( btScalar maxJumpHeight ); + bool canJump() const; + + void jump(); + + void setGravity( btScalar gravity ); + btScalar getGravity() const; + + /// The max slope determines the maximum angle that the controller can walk up. + /// The slope angle is measured in radians. + void setMaxSlope( btScalar slopeRadians ); + btScalar getMaxSlope() const; + + void setUseGhostSweepTest( bool useGhostObjectSweepTest ) + { + m_useGhostObjectSweepTest = useGhostObjectSweepTest; + } + + bool onGround() const; + + //if set to false, there will be no collision. + bool mCollision; +}; + +#endif // KINEMATIC_CHARACTER_CONTROLLER_H diff --git a/libs/openengine/bullet/physic.cpp b/libs/openengine/bullet/physic.cpp new file mode 100644 index 000000000..07bad3053 --- /dev/null +++ b/libs/openengine/bullet/physic.cpp @@ -0,0 +1,375 @@ +#include "physic.hpp" +#include +#include +#include +//#include +#include "CMotionState.h" +#include "OgreRoot.h" +#include "btKinematicCharacterController.h" +#include "BtOgrePG.h" +#include "BtOgreGP.h" +#include "BtOgreExtras.h" + +#define BIT(x) (1<<(x)) + +namespace OEngine { +namespace Physic +{ + enum collisiontypes { + COL_NOTHING = 0, //setWorldTransform( transform ); + + btScalar externalCapsuleHeight = 120; + btScalar externalCapsuleWidth = 19; + + externalCollisionShape = new btCapsuleShapeZ( externalCapsuleWidth, externalCapsuleHeight ); + externalCollisionShape->setMargin( 0.1 ); + + externalGhostObject->setCollisionShape( externalCollisionShape ); + externalGhostObject->setCollisionFlags( btCollisionObject::CF_CHARACTER_OBJECT ); + + // Internal capsule + internalGhostObject = new PairCachingGhostObject(name); + internalGhostObject->setWorldTransform( transform ); + //internalGhostObject->getBroadphaseHandle()->s + btScalar internalCapsuleHeight = 110; + btScalar internalCapsuleWidth = 17; + + internalCollisionShape = new btCapsuleShapeZ( internalCapsuleWidth, internalCapsuleHeight ); + internalCollisionShape->setMargin( 0.1 ); + + internalGhostObject->setCollisionShape( internalCollisionShape ); + internalGhostObject->setCollisionFlags( btCollisionObject::CF_CHARACTER_OBJECT ); + + mCharacter = new btKinematicCharacterController( externalGhostObject,internalGhostObject,btScalar( 40 ),1,4,20,9.8,0.2 ); + mCharacter->setUpAxis(btKinematicCharacterController::Z_AXIS); + mCharacter->setUseGhostSweepTest(false); + + mCharacter->mCollision = false; + setGravity(0); + + mTranslation = btVector3(0,0,70); + } + + PhysicActor::~PhysicActor() + { + delete mCharacter; + delete internalGhostObject; + delete internalCollisionShape; + delete externalGhostObject; + delete externalCollisionShape; + } + + void PhysicActor::setGravity(float gravity) + { + mCharacter->setGravity(gravity); + //mCharacter-> + } + + void PhysicActor::enableCollisions(bool collision) + { + mCharacter->mCollision = collision; + } + + void PhysicActor::setVerticalVelocity(float z) + { + mCharacter->setVerticalVelocity(z); + } + + bool PhysicActor::getCollisionMode() + { + return mCharacter->mCollision; + } + + void PhysicActor::setWalkDirection(const btVector3& mvt) + { + mCharacter->setWalkDirection( mvt ); + } + + void PhysicActor::Rotate(const btQuaternion& quat) + { + externalGhostObject->getWorldTransform().setRotation( externalGhostObject->getWorldTransform().getRotation() * quat ); + internalGhostObject->getWorldTransform().setRotation( internalGhostObject->getWorldTransform().getRotation() * quat ); + } + + void PhysicActor::setRotation(const btQuaternion& quat) + { + externalGhostObject->getWorldTransform().setRotation( quat ); + internalGhostObject->getWorldTransform().setRotation( quat ); + } + + btVector3 PhysicActor::getPosition(void) + { + return internalGhostObject->getWorldTransform().getOrigin() -mTranslation; + } + + btQuaternion PhysicActor::getRotation(void) + { + return internalGhostObject->getWorldTransform().getRotation(); + } + + void PhysicActor::setPosition(const btVector3& pos) + { + internalGhostObject->getWorldTransform().setOrigin(pos+mTranslation); + externalGhostObject->getWorldTransform().setOrigin(pos+mTranslation); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + RigidBody::RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name) + :btRigidBody(CI),mName(name) + { + + }; + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + + + PhysicEngine::PhysicEngine(BulletShapeLoader* shapeLoader) + { + // Set up the collision configuration and dispatcher + collisionConfiguration = new btDefaultCollisionConfiguration(); + dispatcher = new btCollisionDispatcher(collisionConfiguration); + + // The actual physics solver + solver = new btSequentialImpulseConstraintSolver; + + //TODO: memory leak? + btOverlappingPairCache* pairCache = new btSortedOverlappingPairCache(); + //pairCache->setInternalGhostPairCallback( new btGhostPairCallback() ); + + broadphase = new btDbvtBroadphase(pairCache); + + // The world. + dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration); + dynamicsWorld->setGravity(btVector3(0,0,-10)); + + if(BulletShapeManager::getSingletonPtr() == NULL) + { + new BulletShapeManager(); + } + //TODO:singleton? + mShapeLoader = shapeLoader; + + isDebugCreated = false; + } + + void PhysicEngine::createDebugRendering() + { + if(!isDebugCreated) + { + Ogre::SceneManagerEnumerator::SceneManagerIterator iter = Ogre::Root::getSingleton().getSceneManagerIterator(); + iter.begin(); + Ogre::SceneManager* scn = iter.getNext(); + Ogre::SceneNode* node = scn->getRootSceneNode()->createChildSceneNode(); + node->pitch(Ogre::Degree(-90)); + mDebugDrawer = new BtOgre::DebugDrawer(node, dynamicsWorld); + dynamicsWorld->setDebugDrawer(mDebugDrawer); + isDebugCreated = true; + dynamicsWorld->debugDrawWorld(); + } + } + + void PhysicEngine::setDebugRenderingMode(int mode) + { + if(!isDebugCreated) + { + createDebugRendering(); + } + mDebugDrawer->setDebugMode(mode); + } + + PhysicEngine::~PhysicEngine() + { + delete dynamicsWorld; + delete solver; + delete collisionConfiguration; + delete dispatcher; + delete broadphase; + delete mShapeLoader; + } + + RigidBody* PhysicEngine::createRigidBody(std::string mesh,std::string name,float scale) + { + //get the shape from the .nif + mShapeLoader->load(mesh,"General"); + BulletShapeManager::getSingletonPtr()->load(mesh,"General"); + BulletShapePtr shape = BulletShapeManager::getSingleton().getByName(mesh,"General"); + shape->Shape->setLocalScaling(btVector3(scale,scale,scale)); + + //create the motionState + CMotionState* newMotionState = new CMotionState(this,name); + + //create the real body + btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo(0,newMotionState,shape->Shape); + RigidBody* body = new RigidBody(CI,name); + body->collide = shape->collide; + return body; + } + + void PhysicEngine::addRigidBody(RigidBody* body) + { + if(body->collide) + { + dynamicsWorld->addRigidBody(body,COL_WORLD,COL_WORLD|COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL); + } + else + { + dynamicsWorld->addRigidBody(body,COL_WORLD,COL_NOTHING); + } + body->setActivationState(DISABLE_DEACTIVATION); + RigidBodyMap[body->mName] = body; + } + + void PhysicEngine::removeRigidBody(std::string name) + { + std::map::iterator it = RigidBodyMap.find(name); + if (it != RigidBodyMap.end() ) + { + RigidBody* body = it->second; + if(body != NULL) + { + // broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); + /*std::map::iterator it2 = PhysicActorMap.begin(); + for(;it2!=PhysicActorMap.end();it++) + { + it2->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); + it2->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher); + }*/ + dynamicsWorld->removeRigidBody(RigidBodyMap[name]); + } + } + } + + void PhysicEngine::deleteRigidBody(std::string name) + { + std::map::iterator it = RigidBodyMap.find(name); + if (it != RigidBodyMap.end() ) + { + RigidBody* body = it->second; + if(body != NULL) + { + delete body; + } + RigidBodyMap.erase(it); + } + } + + RigidBody* PhysicEngine::getRigidBody(std::string name) + { + RigidBody* body = RigidBodyMap[name]; + return body; + } + + void PhysicEngine::stepSimulation(double deltaT) + { + dynamicsWorld->stepSimulation(deltaT,1,1/50.); + if(isDebugCreated) + { + mDebugDrawer->step(); + } + } + + void PhysicEngine::addCharacter(std::string name) + { + PhysicActor* newActor = new PhysicActor(name); + dynamicsWorld->addCollisionObject( newActor->externalGhostObject, COL_ACTOR_EXTERNAL, COL_WORLD |COL_ACTOR_EXTERNAL ); + dynamicsWorld->addCollisionObject( newActor->internalGhostObject, COL_ACTOR_INTERNAL, COL_WORLD |COL_ACTOR_INTERNAL ); + dynamicsWorld->addAction( newActor->mCharacter ); + PhysicActorMap[name] = newActor; + } + + void PhysicEngine::removeCharacter(std::string name) + { + //std::cout << "remove"; + std::map::iterator it = PhysicActorMap.find(name); + if (it != PhysicActorMap.end() ) + { + PhysicActor* act = it->second; + if(act != NULL) + { + /*broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); + broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); + std::map::iterator it2 = PhysicActorMap.begin(); + for(;it2!=PhysicActorMap.end();it++) + { + it->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); + it->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->externalGhostObject->getBroadphaseHandle(),dispatcher); + it->second->internalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); + it->second->externalGhostObject->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(act->internalGhostObject->getBroadphaseHandle(),dispatcher); + }*/ + //act->externalGhostObject-> + dynamicsWorld->removeCollisionObject(act->externalGhostObject); + dynamicsWorld->removeCollisionObject(act->internalGhostObject); + dynamicsWorld->removeAction(act->mCharacter); + delete act; + } + PhysicActorMap.erase(it); + } + //std::cout << "ok"; + } + + PhysicActor* PhysicEngine::getCharacter(std::string name) + { + PhysicActor* act = PhysicActorMap[name]; + return act; + } + + void PhysicEngine::emptyEventLists(void) + { + } + + std::pair PhysicEngine::rayTest(btVector3& from,btVector3& to) + { + std::string name = ""; + float d = -1; + + float d1 = 10000.; + btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to); + resultCallback1.m_collisionFilterMask = COL_WORLD; + dynamicsWorld->rayTest(from, to, resultCallback1); + if (resultCallback1.hasHit()) + { + name = static_cast(*resultCallback1.m_collisionObject).mName; + d1 = resultCallback1.m_closestHitFraction; + d = d1; + } + + btCollisionWorld::ClosestRayResultCallback resultCallback2(from, to); + resultCallback2.m_collisionFilterMask = COL_ACTOR_INTERNAL|COL_ACTOR_EXTERNAL; + dynamicsWorld->rayTest(from, to, resultCallback2); + float d2 = 10000.; + if (resultCallback2.hasHit()) + { + d2 = resultCallback1.m_closestHitFraction; + if(d2<=d1) + { + name = static_cast(*resultCallback2.m_collisionObject).mName; + d = d2; + } + } + + return std::pair(name,d); + } +}}; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp new file mode 100644 index 000000000..88e3699ae --- /dev/null +++ b/libs/openengine/bullet/physic.hpp @@ -0,0 +1,230 @@ +#ifndef OENGINE_BULLET_PHYSIC_H +#define OENGINE_BULLET_PHYSIC_H + +#include +#include "BulletCollision/CollisionDispatch/btGhostObject.h" +#include +#include +#include +#include "BulletShapeLoader.h" + +class btRigidBody; +class btBroadphaseInterface; +class btDefaultCollisionConfiguration; +class btSequentialImpulseConstraintSolver; +class btCollisionDispatcher; +class btDiscreteDynamicsWorld; +class btKinematicCharacterController; + +namespace BtOgre +{ + class DebugDrawer; +} + +namespace MWWorld +{ + class World; +} + +namespace OEngine { +namespace Physic +{ + class CMotionState; + struct PhysicEvent; + + /** + *This is just used to be able to name objects. + */ + class PairCachingGhostObject : public btPairCachingGhostObject + { + public: + PairCachingGhostObject(std::string name) + :btPairCachingGhostObject(),mName(name) + { + } + std::string mName; + }; + + /** + * A physic Actor use a modifed KinematicCharacterController taken in the bullet forum. + */ + class PhysicActor + { + public: + PhysicActor(std::string name); + + ~PhysicActor(); + + /** + * This function set the walkDirection. This is not relative to the actor orientation. + * I think it's also needed to take time into account. A typical call should look like this: + * setWalkDirection( mvt * orientation * dt) + */ + void setWalkDirection(const btVector3& mvt); + + void Rotate(const btQuaternion& quat); + + void setRotation(const btQuaternion& quat); + + void setGravity(float gravity); + + void setVerticalVelocity(float z); + + void enableCollisions(bool collision); + + bool getCollisionMode(); + + btVector3 getPosition(void); + + btQuaternion getRotation(void); + + void setPosition(const btVector3& pos); + + btKinematicCharacterController* mCharacter; + + PairCachingGhostObject* internalGhostObject; + btCollisionShape* internalCollisionShape; + + PairCachingGhostObject* externalGhostObject; + btCollisionShape* externalCollisionShape; + + std::string mName; + + /** + *NPC scenenode is located on there feet, and you can't simply translate a btShape, so this vector is used + *each time get/setposition is called. + */ + btVector3 mTranslation; + }; + + /** + *This class is just an extension of normal btRigidBody in order to add extra info. + *When bullet give back a btRigidBody, you can just do a static_cast to RigidBody, + *so one never should use btRigidBody directly! + */ + class RigidBody: public btRigidBody + { + public: + RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); + std::string mName; + + //is this body used for raycasting only? + bool collide; + }; + + /** + * The PhysicEngine class contain everything which is needed for Physic. + * It's needed that Ogre Resources are set up before the PhysicEngine is created. + * Note:deleting it WILL NOT delete the RigidBody! + * TODO:unload unused resources? + */ + class PhysicEngine + { + public: + /** + * Note that the shapeLoader IS destroyed by the phyic Engine!! + */ + PhysicEngine(BulletShapeLoader* shapeLoader); + + /** + * It DOES destroy the shape loader! + */ + ~PhysicEngine(); + + /** + * Create a RigidBody.It does not add it to the simulation, but it does add it to the rigidBody Map, + * so you can get it with the getRigidBody function. + */ + RigidBody* createRigidBody(std::string mesh,std::string name,float scale); + + /** + * Add a RigidBody to the simulation + */ + void addRigidBody(RigidBody* body); + + /** + * Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap. + */ + void removeRigidBody(std::string name); + + /** + * Delete a RigidBody, and remove it from RigidBodyMap. + */ + void deleteRigidBody(std::string name); + + /** + * Return a pointer to a given rigid body. + * TODO:check if exist + */ + RigidBody* getRigidBody(std::string name); + + /** + * Create and add a character to the scene, and add it to the ActorMap. + */ + void addCharacter(std::string name); + + /** + * Remove a character from the scene. TODO:delete it! for now, a small memory leak^^ done? + */ + void removeCharacter(std::string name); + + /** + * Return a pointer to a character + * TODO:check if the actor exist... + */ + PhysicActor* getCharacter(std::string name); + + /** + * This step the simulation of a given time. + */ + void stepSimulation(double deltaT); + + /** + * Empty events lists + */ + void emptyEventLists(void); + + /** + * Create a debug rendering. It is called by setDebgRenderingMode if it's not created yet. + * Important Note: this will crash if the Render is not yet initialise! + */ + void createDebugRendering(); + + /** + * Set the debug rendering mode. 0 to turn it off. + * Important Note: this will crash if the Render is not yet initialise! + */ + void setDebugRenderingMode(int mode); + + /** + * Return the closest object hit by a ray. If there are no objects, it will return ("",-1). + */ + std::pair rayTest(btVector3& from,btVector3& to); + + //event list of non player object + std::list NPEventList; + + //event list affecting the player + std::list PEventList; + + //Bullet Stuff + btBroadphaseInterface* broadphase; + btDefaultCollisionConfiguration* collisionConfiguration; + btSequentialImpulseConstraintSolver* solver; + btCollisionDispatcher* dispatcher; + btDiscreteDynamicsWorld* dynamicsWorld; + + //the NIF file loader. + BulletShapeLoader* mShapeLoader; + + std::map RigidBodyMap; + std::map PhysicActorMap; + + //debug rendering + BtOgre::DebugDrawer* mDebugDrawer; + bool isDebugCreated; + }; + +}} + +#endif diff --git a/libs/openengine/gui/events.cpp b/libs/openengine/gui/events.cpp new file mode 100644 index 000000000..713db8585 --- /dev/null +++ b/libs/openengine/gui/events.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +#include "events.hpp" + +using namespace OIS; +using namespace OEngine::GUI; + +EventInjector::EventInjector(MyGUI::Gui *g) + : gui(g), mouseX(0), mouseY(0), enabled(true) +{ + assert(gui); + maxX = gui->getViewSize().width; + maxY = gui->getViewSize().height; +} + +template +void setRange(X &x, X min, X max) +{ + if(x < min) x = min; + else if(x > max) x = max; +} + +void EventInjector::event(Type type, int index, const void *p) +{ + if(!enabled) return; + + if(type & EV_Keyboard) + { + KeyEvent *key = (KeyEvent*)p; + MyGUI::KeyCode code = MyGUI::KeyCode::Enum(key->key); + if(type == EV_KeyDown) + { + /* + This is just a first approximation. Apparently, OIS is + unable to provide reliable unicode characters on all + platforms. At least that's what I surmise from the amount + of workaround that the MyGUI folks have put in place for + this. See Common/Input/OIS/InputManager.cpp in the MyGUI + sources for details. + + If the work they have done there is indeed necessary (I + haven't tested that it is, although I have had dubious + experinces with OIS events in the past), then we should + probably adapt all that code here. Or even better, + directly into the OIS input manager in Mangle. + + Note that all this only affects the 'text' field, and + should thus only affect typed text in input boxes (which + is still pretty significant.) + */ + MyGUI::Char text = (MyGUI::Char)key->text; + gui->injectKeyPress(code,text); + } + else + { + gui->injectKeyRelease(code); + } + } + else if(type & EV_Mouse) + { + MouseEvent *mouse = (MouseEvent*)p; + MyGUI::MouseButton id = MyGUI::MouseButton::Enum(index); + + // Update mouse position + mouseX += mouse->state.X.rel; + mouseY += mouse->state.Y.rel; + + setRange(mouseX,0,maxX); + setRange(mouseY,0,maxY); + + if(type == EV_MouseDown) + gui->injectMousePress(mouseX, mouseY, id); + else if(type == EV_MouseUp) + gui->injectMouseRelease(mouseX, mouseY, id); + else + gui->injectMouseMove(mouseX, mouseY, mouse->state.Z.abs); + } +} diff --git a/libs/openengine/gui/events.hpp b/libs/openengine/gui/events.hpp new file mode 100644 index 000000000..6ca83cf75 --- /dev/null +++ b/libs/openengine/gui/events.hpp @@ -0,0 +1,31 @@ +#ifndef OENGINE_MYGUI_EVENTS_H +#define OENGINE_MYGUI_EVENTS_H + +#include + +namespace MyGUI +{ + class Gui; +} + +namespace OEngine { +namespace GUI +{ + /** Event handler that injects OIS events into MyGUI + */ + class EventInjector : public Mangle::Input::Event + { + MyGUI::Gui *gui; + int mouseX, mouseY; + int maxX, maxY; + + public: + bool enabled; + + EventInjector(MyGUI::Gui *g); + void event(Type type, int index, const void *p); + }; + + typedef boost::shared_ptr EventInjectorPtr; +}} +#endif diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp new file mode 100644 index 000000000..f02ddbdff --- /dev/null +++ b/libs/openengine/gui/layout.hpp @@ -0,0 +1,119 @@ +#ifndef OENGINE_MYGUI_LAYOUT_H +#define OENGINE_MYGUI_LAYOUT_H + +#include +#include + +namespace OEngine { +namespace GUI +{ + /** The Layout class is an utility class used to load MyGUI layouts + from xml files, and to manipulate member widgets. + */ + class Layout + { + public: + Layout(const std::string & _layout, MyGUI::WidgetPtr _parent = nullptr) + : mMainWidget(nullptr) + { initialise(_layout, _parent); } + virtual ~Layout() { shutdown(); } + + template + void getWidget(T * & _widget, const std::string & _name, bool _throw = true) + { + _widget = nullptr; + for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin(); + iter!=mListWindowRoot.end(); ++iter) + { + MyGUI::WidgetPtr find = (*iter)->findWidget(mPrefix + _name); + if (nullptr != find) + { + T * cast = find->castType(false); + if (nullptr != cast) + _widget = cast; + else if (_throw) + { + MYGUI_EXCEPT("Error cast : dest type = '" << T::getClassTypeName() + << "' source name = '" << find->getName() + << "' source type = '" << find->getTypeName() << "' in layout '" << mLayoutName << "'"); + } + return; + } + } + MYGUI_ASSERT( ! _throw, "widget name '" << _name << "' in layout '" << mLayoutName << "' not found."); + } + + void initialise(const std::string & _layout, + MyGUI::WidgetPtr _parent = nullptr) + { + const std::string MAIN_WINDOW = "_Main"; + mLayoutName = _layout; + + if (mLayoutName.empty()) + mMainWidget = _parent; + else + { + mPrefix = MyGUI::utility::toString(this, "_"); + mListWindowRoot = MyGUI::LayoutManager::getInstance().loadLayout(mLayoutName, mPrefix, _parent); + + const std::string main_name = mPrefix + MAIN_WINDOW; + for (MyGUI::VectorWidgetPtr::iterator iter=mListWindowRoot.begin(); iter!=mListWindowRoot.end(); ++iter) + { + if ((*iter)->getName() == main_name) + { + mMainWidget = (*iter); + break; + } + } + MYGUI_ASSERT(mMainWidget, "root widget name '" << MAIN_WINDOW << "' in layout '" << mLayoutName << "' not found."); + } + } + + void shutdown() + { + MyGUI::LayoutManager::getInstance().unloadLayout(mListWindowRoot); + mListWindowRoot.clear(); + } + + void setCoord(int x, int y, int w, int h) + { + mMainWidget->setCoord(x,y,w,h); + } + + void setVisible(bool b) + { + mMainWidget->setVisible(b); + } + + void setText(const std::string& name, const std::string& caption) + { + MyGUI::WidgetPtr pt; + getWidget(pt, name); + pt->setCaption(caption); + } + + void setTextColor(const std::string& name, float r, float g, float b) + { + MyGUI::WidgetPtr pt; + getWidget(pt, name); + MyGUI::StaticText *st = dynamic_cast(pt); + if(st != NULL) + st->setTextColour(MyGUI::Colour(b,g,r)); + } + + void setImage(const std::string& name, const std::string& imgName) + { + MyGUI::StaticImagePtr pt; + getWidget(pt, name); + pt->setImageTexture(imgName); + } + + protected: + + MyGUI::WidgetPtr mMainWidget; + std::string mPrefix; + std::string mLayoutName; + MyGUI::VectorWidgetPtr mListWindowRoot; + }; +}} +#endif diff --git a/libs/openengine/gui/manager.cpp b/libs/openengine/gui/manager.cpp new file mode 100644 index 000000000..b606d7d16 --- /dev/null +++ b/libs/openengine/gui/manager.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +#include "manager.hpp" + +using namespace OEngine::GUI; + +void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging, const std::string& logDir) +{ + assert(wnd); + assert(mgr); + + using namespace MyGUI; + + // Enable/disable MyGUI logging to stdout. (Logging to MyGUI.log is + // still enabled.) In order to do this we have to initialize the log + // manager before the main gui system itself, otherwise the main + // object will get the chance to spit out a few messages before we + // can able to disable it. + LogManager::initialise(); + LogManager::setSTDOutputEnabled(logging); + + std::string theLogFile = std::string(MYGUI_PLATFORM_LOG_FILENAME); + if(!logDir.empty()) + theLogFile.insert(0, logDir); + + // Set up OGRE platform. We might make this more generic later. + mPlatform = new OgrePlatform(); + mPlatform->initialise(wnd, mgr, "General", theLogFile); + + // Create GUI + mGui = new Gui(); + mGui->initialise("core.xml", theLogFile); +} + +void MyGUIManager::shutdown() +{ + if(mGui) delete mGui; + if(mPlatform) + { + mPlatform->shutdown(); + delete mPlatform; + } + mGui = NULL; + mPlatform = NULL; +} diff --git a/libs/openengine/gui/manager.hpp b/libs/openengine/gui/manager.hpp new file mode 100644 index 000000000..781685acf --- /dev/null +++ b/libs/openengine/gui/manager.hpp @@ -0,0 +1,36 @@ +#ifndef OENGINE_MYGUI_MANAGER_H +#define OENGINE_MYGUI_MANAGER_H + +namespace MyGUI +{ + class OgrePlatform; + class Gui; +} + +namespace Ogre +{ + class RenderWindow; + class SceneManager; +} + +namespace OEngine { +namespace GUI +{ + class MyGUIManager + { + MyGUI::OgrePlatform *mPlatform; + MyGUI::Gui *mGui; + + public: + MyGUIManager() : mPlatform(NULL), mGui(NULL) {} + MyGUIManager(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")) + { setup(wnd,mgr,logging, logDir); } + ~MyGUIManager() { shutdown(); } + + void setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false, const std::string& logDir = std::string("")); + void shutdown(); + + MyGUI::Gui *getGui() { return mGui; } + }; +}} +#endif diff --git a/libs/openengine/input/dispatch_map.hpp b/libs/openengine/input/dispatch_map.hpp new file mode 100644 index 000000000..f0d4cabe9 --- /dev/null +++ b/libs/openengine/input/dispatch_map.hpp @@ -0,0 +1,75 @@ +#ifndef OENGINE_INPUT_DISPATCHMAP_H +#define OENGINE_INPUT_DISPATCHMAP_H + +#include +#include +#include + +namespace OEngine { +namespace Input { + +/** + DispatchMap is a simple connection system that connects incomming + signals with outgoing signals. + + The signals can be connected one-to-one, many-to-one, one-to-many + or many-to-many. + + The dispatch map is completely system agnostic. It is a pure data + structure and all signals are just integer indices. It does not + delegate any actions, but used together with Dispatcher it can be + used to build an event system. + */ +struct DispatchMap +{ + typedef std::set OutList; + typedef std::map InMap; + + typedef OutList::iterator Oit; + typedef InMap::iterator Iit; + + InMap map; + + void bind(int in, int out) + { + map[in].insert(out); + } + + void unbind(int in, int out) + { + Iit it = map.find(in); + if(it != map.end()) + { + it->second.erase(out); + + // If there are no more elements, then remove the entire list + if(it->second.empty()) + map.erase(it); + } + } + + /// Check if a given input is bound to anything + bool isBound(int in) const + { + return map.find(in) != map.end(); + } + + /** + Get the list of outputs bound to the given input. Only call this + on inputs that you know are bound to something. + + The returned set is only intended for immediate iteration. Do not + store references to it. + */ + const OutList &getList(int in) const + { + assert(isBound(in)); + InMap::const_iterator it = map.find(in); + assert(it != map.end()); + const OutList &out = it->second; + assert(!out.empty()); + return out; + } +}; +}} +#endif diff --git a/libs/openengine/input/dispatcher.hpp b/libs/openengine/input/dispatcher.hpp new file mode 100644 index 000000000..a8d480d4b --- /dev/null +++ b/libs/openengine/input/dispatcher.hpp @@ -0,0 +1,78 @@ +#ifndef OENGINE_INPUT_DISPATCHER_H +#define OENGINE_INPUT_DISPATCHER_H + +#include +#include + +#include + +#include "dispatch_map.hpp" +#include "func_binder.hpp" + +namespace OEngine { +namespace Input { + +struct Dispatcher : Mangle::Input::Event +{ + DispatchMap map; + FuncBinder funcs; + + /** + Constructor. Takes the number of actions and passes it to + FuncBinder. + */ + Dispatcher(int actions) : funcs(actions) {} + + void bind(unsigned int action, int key) + { + assert(action < funcs.getSize()); + map.bind(key, action); + } + void unbind(unsigned int action, int key) + { + assert(action < funcs.getSize()); + map.unbind(key, action); + } + bool isBound(int key) const { return map.isBound(key); } + + /** + Instigate an event. It is translated through the dispatch map and + sent to the function bindings. + */ + typedef DispatchMap::OutList _O; + void event(Type type, int index, const void* p) + { + // No bindings, nothing happens + if(!isBound(index)) + return; + + // Get the mapped actions and execute them + const _O &list = map.getList(index); + _O::const_iterator it; + for(it = list.begin(); it != list.end(); it++) + { + //catch exceptions thrown in the input handlers so that pressing a key + //doesn't cause OpenMw to crash + try + { + funcs.call(*it, p); + } + catch(const std::exception& e) + { + std::cerr << "Exception in input handler: " << e.what() << std::endl; + } + catch(...) + { + std::cerr << "Unknown exception in input handler" << std::endl; + } + + } + } +}; + +// This helps us play nice with Mangle's EventPtr, but it should +// really be defined for all the classes in OEngine. + typedef boost::shared_ptr DispatcherPtr; + +}} +#endif diff --git a/libs/openengine/input/func_binder.hpp b/libs/openengine/input/func_binder.hpp new file mode 100644 index 000000000..7aa733edf --- /dev/null +++ b/libs/openengine/input/func_binder.hpp @@ -0,0 +1,106 @@ +#ifndef OENGINE_INPUT_FUNCBINDER_H +#define OENGINE_INPUT_FUNCBINDER_H + +#include +#include +#include +#include + +namespace OEngine { +namespace Input { + +/** + An Action defines the user defined action corresponding to a + binding. + + The first parameter is the action index that invoked this call. You + can assign the same function to multiple actions, and this can help + you keep track of which action was invoked. + + The second parameter is an optional user-defined parameter, + represented by a void pointer. In many cases it is practical to + point this to temporaries (stack values), so make sure not to store + permanent references to it unless you've planning for this on the + calling side as well. + */ +typedef boost::function Action; + +/** + The FuncBinder is a simple struct that binds user-defined indices + to functions. It is useful for binding eg. keyboard events to + specific actions in your program, but can potentially have many + other uses as well. + */ +class FuncBinder +{ + struct FuncBinding + { + std::string name; + Action action; + }; + + std::vector bindings; + +public: + /** + Constructor. Initialize the struct by telling it how many action + indices you intend to bind. + + The indices you use should be 0 <= i < number. + */ + FuncBinder(int number) : bindings(number) {} + + unsigned int getSize() { return bindings.size(); } + + /** + Bind an action to an index. + */ + void bind(int index, Action action, const std::string &name="") + { + assert(index >= 0 && index < (int)bindings.size()); + + FuncBinding &fb = bindings[index]; + fb.action = action; + fb.name = name; + } + + /** + Unbind an index, reverting a previous bind(). + */ + void unbind(int index) + { + assert(index >= 0 && index < (int)bindings.size()); + + bindings[index] = FuncBinding(); + } + + /** + Call a specific action. Takes an optional parameter that is + passed to the action. + */ + void call(int index, const void *p=NULL) const + { + assert(index >= 0 && index < (int)bindings.size()); + + const FuncBinding &fb = bindings[index]; + if(fb.action) fb.action(index, p); + } + + /// Check if a given index is bound to anything + bool isBound(int index) const + { + assert(index >= 0 && index < (int)bindings.size()); + + return !bindings[index].action.empty(); + } + + /// Return the name associated with an action (empty if not bound) + const std::string &getName(int index) const + { + assert(index >= 0 && index < (int)bindings.size()); + + return bindings[index].name; + } +}; +}} +#endif diff --git a/libs/openengine/input/poller.hpp b/libs/openengine/input/poller.hpp new file mode 100644 index 000000000..c544aed52 --- /dev/null +++ b/libs/openengine/input/poller.hpp @@ -0,0 +1,46 @@ +#ifndef OENGINE_INPUT_POLLER_H +#define OENGINE_INPUT_POLLER_H + +#include "dispatch_map.hpp" +#include + +namespace OEngine { +namespace Input { + +/** The poller is used to check (poll) for keys rather than waiting + for events. */ +struct Poller +{ + DispatchMap map; + Mangle::Input::Driver &input; + + Poller(Mangle::Input::Driver &drv) + : input(drv) {} + + /** Bind or unbind a given action with a key. The action is the first + parameter, the key is the second. + */ + void bind(int in, int out) { map.bind(in, out); } + void unbind(int in, int out) { map.unbind(in, out); } + bool isBound(int in) const { return map.isBound(in); } + + /// Check whether a given action button is currently pressed. + typedef DispatchMap::OutList _O; + bool isDown(int index) const + { + // No bindings, no action + if(!isBound(index)) + return false; + + // Get all the keys bound to this action, and check them. + const _O &list = map.getList(index); + _O::const_iterator it; + for(it = list.begin(); it != list.end(); it++) + // If there's any match, we're good to go. + if(input.isDown(*it)) return true; + + return false; + } +}; +}} +#endif diff --git a/libs/openengine/input/tests/Makefile b/libs/openengine/input/tests/Makefile new file mode 100644 index 000000000..91a0b2663 --- /dev/null +++ b/libs/openengine/input/tests/Makefile @@ -0,0 +1,18 @@ +GCC=g++ + +all: funcbind_test dispatch_map_test sdl_driver_test sdl_binder_test + +funcbind_test: funcbind_test.cpp ../func_binder.hpp + $(GCC) $< -o $@ + +dispatch_map_test: dispatch_map_test.cpp ../dispatch_map.hpp + $(GCC) $< -o $@ + +sdl_driver_test: sdl_driver_test.cpp + $(GCC) $< ../../mangle/input/servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -I../../ + +sdl_binder_test: sdl_binder_test.cpp + $(GCC) $< ../../mangle/input/servers/sdl_driver.cpp -o $@ -I/usr/include/SDL/ -lSDL -I../../ + +clean: + rm *_test diff --git a/libs/openengine/input/tests/dispatch_map_test.cpp b/libs/openengine/input/tests/dispatch_map_test.cpp new file mode 100644 index 000000000..5f262494e --- /dev/null +++ b/libs/openengine/input/tests/dispatch_map_test.cpp @@ -0,0 +1,54 @@ +#include +using namespace std; + +#include "../dispatch_map.hpp" + +using namespace OEngine::Input; + +typedef DispatchMap::OutList OutList; +typedef OutList::const_iterator Cit; + +void showList(const DispatchMap::OutList &out) +{ + for(Cit it = out.begin(); + it != out.end(); it++) + { + cout << " " << *it << endl; + } +} + +void showAll(DispatchMap &map) +{ + cout << "\nPrinting everything:\n"; + for(DispatchMap::Iit it = map.map.begin(); + it != map.map.end(); it++) + { + cout << it->first << ":\n"; + showList(map.getList(it->first)); + } +} + +int main() +{ + cout << "Testing the dispatch map\n"; + + DispatchMap dsp; + + dsp.bind(1,9); + dsp.bind(2,-5); + dsp.bind(2,9); + dsp.bind(3,10); + dsp.bind(3,12); + dsp.bind(3,10); + + showAll(dsp); + + dsp.unbind(1,9); + dsp.unbind(5,8); + dsp.unbind(3,11); + dsp.unbind(3,12); + dsp.unbind(3,12); + + showAll(dsp); + return 0; +} diff --git a/libs/openengine/input/tests/funcbind_test.cpp b/libs/openengine/input/tests/funcbind_test.cpp new file mode 100644 index 000000000..bb4ad34e1 --- /dev/null +++ b/libs/openengine/input/tests/funcbind_test.cpp @@ -0,0 +1,47 @@ +#include +using namespace std; + +#include "../func_binder.hpp" + +void f1(int i, const void *p) +{ + cout << " F1 i=" << i << endl; + + if(p) + cout << " Got a nice gift: " + << *((const float*)p) << endl; +} + +void f2(int i, const void *p) +{ + cout << " F2 i=" << i << endl; +} + +using namespace OEngine::Input; + +int main() +{ + cout << "This will test the function binding system\n"; + + FuncBinder bnd(5); + + bnd.bind(0, &f1, "This is action 1"); + bnd.bind(1, &f2); + bnd.bind(2, &f1, "This is action 3"); + bnd.bind(3, &f2, "This is action 4"); + + bnd.unbind(2); + + for(int i=0; i<5; i++) + { + cout << "Calling " << i << ": '" << bnd.getName(i) << "'\n"; + bnd.call(i); + if(!bnd.isBound(i)) cout << " (not bound)\n"; + } + + cout << "\nCalling with parameter:\n"; + float f = 3.1415; + bnd.call(0, &f); + + return 0; +} diff --git a/libs/openengine/input/tests/output/dispatch_map_test.out b/libs/openengine/input/tests/output/dispatch_map_test.out new file mode 100644 index 000000000..01aa9d9d9 --- /dev/null +++ b/libs/openengine/input/tests/output/dispatch_map_test.out @@ -0,0 +1,18 @@ +Testing the dispatch map + +Printing everything: +1: + 9 +2: + -5 + 9 +3: + 10 + 12 + +Printing everything: +2: + -5 + 9 +3: + 10 diff --git a/libs/openengine/input/tests/output/funcbind_test.out b/libs/openengine/input/tests/output/funcbind_test.out new file mode 100644 index 000000000..862c5c972 --- /dev/null +++ b/libs/openengine/input/tests/output/funcbind_test.out @@ -0,0 +1,15 @@ +This will test the function binding system +Calling 0: 'This is action 1' + F1 i=0 +Calling 1: '' + F2 i=1 +Calling 2: '' + (not bound) +Calling 3: 'This is action 4' + F2 i=3 +Calling 4: '' + (not bound) + +Calling with parameter: + F1 i=0 + Got a nice gift: 3.1415 diff --git a/libs/openengine/input/tests/output/sdl_binder_test.out b/libs/openengine/input/tests/output/sdl_binder_test.out new file mode 100644 index 000000000..fd4eb90e3 --- /dev/null +++ b/libs/openengine/input/tests/output/sdl_binder_test.out @@ -0,0 +1,4 @@ +Hold the Q key to quit: +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/openengine/input/tests/output/sdl_driver_test.out b/libs/openengine/input/tests/output/sdl_driver_test.out new file mode 100644 index 000000000..fd4eb90e3 --- /dev/null +++ b/libs/openengine/input/tests/output/sdl_driver_test.out @@ -0,0 +1,4 @@ +Hold the Q key to quit: +You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly + +Bye bye! diff --git a/libs/openengine/input/tests/sdl_binder_test.cpp b/libs/openengine/input/tests/sdl_binder_test.cpp new file mode 100644 index 000000000..7de5f5d4f --- /dev/null +++ b/libs/openengine/input/tests/sdl_binder_test.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include "../dispatcher.hpp" +#include "../poller.hpp" + +using namespace std; +using namespace Mangle::Input; +using namespace OEngine::Input; + +enum Actions + { + A_Quit, + A_Left, + A_Right, + + A_LAST + }; + +bool quit=false; + +void doExit(int,const void*) +{ + quit = true; +} + +void goLeft(int,const void*) +{ + cout << "Going left\n"; +} + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + SDLDriver input; + Dispatcher *disp = new Dispatcher(A_LAST); + Poller poll(input); + + input.setEvent(EventPtr(disp)); + + disp->funcs.bind(A_Quit, &doExit); + disp->funcs.bind(A_Left, &goLeft); + + disp->bind(A_Quit, SDLK_q); + disp->bind(A_Left, SDLK_a); + disp->bind(A_Left, SDLK_LEFT); + + poll.bind(A_Right, SDLK_d); + poll.bind(A_Right, SDLK_RIGHT); + + cout << "Hold the Q key to quit:\n"; + //input->setEvent(&mycb); + while(!quit) + { + input.capture(); + if(poll.isDown(A_Right)) + cout << "We're going right!\n"; + SDL_Delay(20); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + cout << "\nBye bye!\n"; + + SDL_Quit(); + return 0; +} diff --git a/libs/openengine/input/tests/sdl_driver_test.cpp b/libs/openengine/input/tests/sdl_driver_test.cpp new file mode 100644 index 000000000..1771bcfe4 --- /dev/null +++ b/libs/openengine/input/tests/sdl_driver_test.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +using namespace std; +using namespace Mangle::Input; + +int main(int argc, char** argv) +{ + SDL_Init(SDL_INIT_VIDEO); + SDL_SetVideoMode(640, 480, 0, SDL_SWSURFACE); + SDLDriver input; + + cout << "Hold the Q key to quit:\n"; + //input->setEvent(&mycb); + while(!input.isDown(SDLK_q)) + { + input.capture(); + SDL_Delay(20); + + if(argc == 1) + { + cout << "You are running in script mode, aborting. Run this test with a parameter (any at all) to test the input loop properly\n"; + break; + } + } + cout << "\nBye bye!\n"; + + SDL_Quit(); + return 0; +} diff --git a/libs/openengine/input/tests/test.sh b/libs/openengine/input/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/openengine/input/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/openengine/misc/list.hpp b/libs/openengine/misc/list.hpp new file mode 100644 index 000000000..b08b57494 --- /dev/null +++ b/libs/openengine/misc/list.hpp @@ -0,0 +1,178 @@ +#ifndef MISC_LIST_H +#define MISC_LIST_H + +#include + +namespace Misc{ + +/* + A simple and completely allocation-less doubly linked list. The + class only manages pointers to and between elements. It leaving all + memory management to the user. +*/ +template +struct List +{ + List() : head(0), tail(0), totalNum(0) {} + + // Empty the list. + void reset() + { + head = 0; + tail = 0; + totalNum = 0; + } + + // Insert an element at the end of the list. The element cannot be + // part of any other list when this is called. + void insert(Elem *p) + { + if(tail) + { + // There are existing elements. Insert the node at the end of + // the list. + assert(head && totalNum > 0); + tail->next = p; + } + else + { + // This is the first element + assert(head == 0 && totalNum == 0); + head = p; + } + + // These have to be done in either case + p->prev = tail; + p->next = 0; + tail = p; + + totalNum++; + } + + // Remove element from the list. The element MUST be part of the + // list when this is called. + void remove(Elem *p) + { + assert(totalNum > 0); + + if(p->next) + { + // There's an element following us. Set it up correctly. + p->next->prev = p->prev; + assert(tail && tail != p); + } + else + { + // We're the tail + assert(tail == p); + tail = p->prev; + } + + // Now do exactly the same for the previous element + if(p->prev) + { + p->prev->next = p->next; + assert(head && head != p); + } + else + { + assert(head == p); + head = p->next; + } + + totalNum--; + } + + // Pop the first element off the list + Elem *pop() + { + Elem *res = getHead(); + if(res) remove(res); + return res; + } + + // Swap the contents of this list with another of the same type + void swap(List &other) + { + Elem *tmp; + + tmp = head; + head = other.head; + other.head = tmp; + + tmp = tail; + tail = other.tail; + other.tail = tmp; + + unsigned int tmp2 = totalNum; + totalNum = other.totalNum; + other.totalNum = tmp2; + } + + /* Absorb the contents of another list. All the elements from the + list are moved to the end of this list, and the other list is + cleared. + */ + void absorb(List &other) + { + assert(&other != this); + if(other.totalNum) + { + absorb(other.head, other.tail, other.totalNum); + other.reset(); + } + assert(other.totalNum == 0); + } + + /* Absorb a range of elements, endpoints included. The elements are + assumed NOT to belong to any list, but they ARE assumed to be + connected with a chain between them. + + The connection MUST run all the way from 'first' to 'last' + through the ->next pointers, and vice versa through ->prev + pointers. + + The parameter 'num' must give the exact number of elements in the + chain. + + Passing first == last, num == 1 is allowed and is equivalent to + calling insert(). + */ + void absorb(Elem* first, Elem *last, int num) + { + assert(first && last && num>=1); + if(tail) + { + // There are existing elements. Insert the first node at the + // end of the list. + assert(head && totalNum > 0); + tail->next = first; + } + else + { + // This is the first element + assert(head == 0 && totalNum == 0); + head = first; + } + + // These have to be done in either case + first->prev = tail; + last->next = 0; + tail = last; + + totalNum += num; + } + + Elem* getHead() const { return head; } + Elem* getTail() const { return tail; } + unsigned int getNum() const { return totalNum; } + +private: + + Elem *head; + Elem *tail; + unsigned int totalNum; +}; + +} +#endif diff --git a/libs/openengine/ogre/.gitignore b/libs/openengine/ogre/.gitignore new file mode 100644 index 000000000..3367afdbb --- /dev/null +++ b/libs/openengine/ogre/.gitignore @@ -0,0 +1 @@ +old diff --git a/libs/openengine/ogre/exitlistener.hpp b/libs/openengine/ogre/exitlistener.hpp new file mode 100644 index 000000000..5a9d1ff68 --- /dev/null +++ b/libs/openengine/ogre/exitlistener.hpp @@ -0,0 +1,37 @@ +#ifndef OENGINE_OGRE_EXITLISTEN_H +#define OENGINE_OGRE_EXITLISTEN_H + +/* + This FrameListener simply exits the rendering loop when the window + is closed. You can also tell it to exit manually by setting the exit + member to true; + */ + +#include +#include + +namespace OEngine { +namespace Render +{ + struct ExitListener : Ogre::FrameListener + { + Ogre::RenderWindow *window; + bool exit; + + ExitListener(Ogre::RenderWindow *wnd) + : window(wnd), exit(false) {} + + bool frameStarted(const Ogre::FrameEvent &evt) + { + if(window->isClosed()) + exit = true; + + return !exit; + } + + // Function equivalent of setting exit=true. Handy when you need a + // delegate to bind to an event. + void exitNow() { exit = true; } + }; +}} +#endif diff --git a/libs/openengine/ogre/fader.cpp b/libs/openengine/ogre/fader.cpp new file mode 100644 index 000000000..062559e00 --- /dev/null +++ b/libs/openengine/ogre/fader.cpp @@ -0,0 +1,136 @@ +#include "fader.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FADE_OVERLAY_NAME "FadeInOutOverlay" +#define FADE_OVERLAY_PANEL_NAME "FadeInOutOverlayPanel" +#define FADE_MATERIAL_NAME "FadeInOutMaterial" + +using namespace Ogre; +using namespace OEngine::Render; + +Fader::Fader() : + mMode(FadingMode_In), + mRemainingTime(0.f), + mTargetTime(0.f), + mTargetAlpha(0.f), + mCurrentAlpha(0.f), + mStartAlpha(0.f) +{ + + // Create the fading material + MaterialPtr material = MaterialManager::getSingleton().create( FADE_MATERIAL_NAME, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME ); + Pass* pass = material->getTechnique(0)->getPass(0); + pass->setSceneBlending(SBT_TRANSPARENT_ALPHA); + mFadeTextureUnit = pass->createTextureUnitState(); + mFadeTextureUnit->setColourOperationEx(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, ColourValue(0.f, 0.f, 0.f)); // always black colour + + // Create the overlay + OverlayManager& ovm = OverlayManager::getSingleton(); + + mOverlay = ovm.create( FADE_OVERLAY_NAME ); + + OverlayContainer* overlay_panel; + overlay_panel = (OverlayContainer*)ovm.createOverlayElement("Panel", FADE_OVERLAY_PANEL_NAME); + + // position it over the whole screen + overlay_panel->_setPosition(0, 0); + overlay_panel->_setDimensions(1, 1); + + overlay_panel->setMaterialName( FADE_MATERIAL_NAME ); + overlay_panel->show(); + mOverlay->add2D(overlay_panel); + mOverlay->hide(); +} + +void Fader::update(float dt) +{ + if (mRemainingTime > 0) + { + if (mMode == FadingMode_In) + { + mCurrentAlpha -= dt/mTargetTime * (mStartAlpha-mTargetAlpha); + if (mCurrentAlpha < mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + else if (mMode == FadingMode_Out) + { + mCurrentAlpha += dt/mTargetTime * (mTargetAlpha-mStartAlpha); + if (mCurrentAlpha > mTargetAlpha) mCurrentAlpha = mTargetAlpha; + } + + applyAlpha(); + + mRemainingTime -= dt; + } + + if (mCurrentAlpha == 0.f) mOverlay->hide(); +} + +void Fader::applyAlpha() +{ + mOverlay->show(); + mFadeTextureUnit->setAlphaOperation(LBX_SOURCE1, LBS_MANUAL, LBS_CURRENT, mCurrentAlpha); +} + +void Fader::fadeIn(float time) +{ + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 0.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 0.f; + mMode = FadingMode_In; + mTargetTime = time; + mRemainingTime = time; +} + +void Fader::fadeOut(const float time) +{ + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = 1.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = 1.f; + mMode = FadingMode_Out; + mTargetTime = time; + mRemainingTime = time; +} + +void Fader::fadeTo(const int percent, const float time) +{ + if (time<0.f) return; + if (time==0.f) + { + mCurrentAlpha = percent/100.f; + applyAlpha(); + return; + } + + mStartAlpha = mCurrentAlpha; + mTargetAlpha = percent/100.f; + + if (mTargetAlpha == mStartAlpha) return; + else if (mTargetAlpha > mStartAlpha) mMode = FadingMode_Out; + else mMode = FadingMode_In; + + mTargetTime = time; + mRemainingTime = time; +} diff --git a/libs/openengine/ogre/fader.hpp b/libs/openengine/ogre/fader.hpp new file mode 100644 index 000000000..f76ac51ef --- /dev/null +++ b/libs/openengine/ogre/fader.hpp @@ -0,0 +1,57 @@ +#ifndef OENGINE_OGRE_FADE_H +#define OENGINE_OGRE_FADE_H + +/* + A class that handles fading in the screen from black or fading it out to black. + + To achieve this, it uses a full-screen Ogre::Overlay + + inspired by http://www.ogre3d.org/tikiwiki/FadeEffectOverlay (heavily adjusted) + */ + +#include + +namespace Ogre +{ + class TextureUnitState; + class Overlay; +} + +namespace OEngine { +namespace Render +{ + class Fader + { + public: + Fader(); + + void update(float dt); + + void fadeIn(const float time); + void fadeOut(const float time); + void fadeTo(const int percent, const float time); + + private: + enum FadingMode + { + FadingMode_In, + FadingMode_Out + }; + + void applyAlpha(); + + Ogre::TextureUnitState* mFadeTextureUnit; + Ogre::Overlay* mOverlay; + + FadingMode mMode; + + float mRemainingTime; + float mTargetTime; + float mTargetAlpha; + float mCurrentAlpha; + float mStartAlpha; + + protected: + }; +}} +#endif diff --git a/libs/openengine/ogre/mouselook.cpp b/libs/openengine/ogre/mouselook.cpp new file mode 100644 index 000000000..841bab603 --- /dev/null +++ b/libs/openengine/ogre/mouselook.cpp @@ -0,0 +1,57 @@ +#include "mouselook.hpp" + +#include +#include +#include + +using namespace OIS; +using namespace Ogre; +using namespace OEngine::Render; + +void MouseLookEvent::event(Type type, int index, const void *p) +{ + if(type != EV_MouseMove || camera == NULL) return; + + MouseEvent *arg = (MouseEvent*)(p); + + float x = arg->state.X.rel * sensX; + float y = arg->state.Y.rel * sensY; + + camera->getParentSceneNode()->getParentSceneNode()->yaw(Degree(-x)); + camera->getParentSceneNode()->pitch(Degree(-y)); + if(flipProt) + { + // The camera before pitching + /*Quaternion nopitch = camera->getParentSceneNode()->getOrientation(); + + camera->getParentSceneNode()->pitch(Degree(-y)); + + // Apply some failsafe measures against the camera flipping + // upside down. Is the camera close to pointing straight up or + // down? + if(Ogre::Vector3(camera->getParentSceneNode()->getOrientation()*Ogre::Vector3::UNIT_Y)[1] <= 0.1) + // If so, undo the last pitch + camera->getParentSceneNode()->setOrientation(nopitch);*/ + //camera->getU + + // Angle of rotation around the X-axis. + float pitchAngle = (2 * Ogre::Degree(Ogre::Math::ACos(camera->getParentSceneNode()->getOrientation().w)).valueDegrees()); + + // Just to determine the sign of the angle we pick up above, the + // value itself does not interest us. + float pitchAngleSign = camera->getParentSceneNode()->getOrientation().x; + + // Limit the pitch between -90 degress and +90 degrees, Quake3-style. + if (pitchAngle > 90.0f) + { + if (pitchAngleSign > 0) + // Set orientation to 90 degrees on X-axis. + camera->getParentSceneNode()->setOrientation(Ogre::Quaternion(Ogre::Math::Sqrt(0.5f), + Ogre::Math::Sqrt(0.5f), 0, 0)); + else if (pitchAngleSign < 0) + // Sets orientation to -90 degrees on X-axis. + camera->getParentSceneNode()->setOrientation(Ogre::Quaternion(Ogre::Math::Sqrt(0.5f), + -Ogre::Math::Sqrt(0.5f), 0, 0)); + } + } +} diff --git a/libs/openengine/ogre/mouselook.hpp b/libs/openengine/ogre/mouselook.hpp new file mode 100644 index 000000000..6e09ff4a1 --- /dev/null +++ b/libs/openengine/ogre/mouselook.hpp @@ -0,0 +1,56 @@ +#ifndef OENGINE_OGRE_MOUSELOOK_H +#define OENGINE_OGRE_MOUSELOOK_H + +/* + A mouse-look class for Ogre. Accepts input events from Mangle::Input + and translates them. + + You can adjust the mouse sensibility and switch to a different + camera. The mouselook class also has an optional wrap protection + that keeps the camera from flipping upside down. + + You can disable the mouse looker at any time by calling + setCamera(NULL), and reenable it by setting the camera back. + + NOTE: The current implementation will ONLY work for native OIS + events. + */ + +#include + +namespace Ogre +{ + class Camera; +} + +namespace OEngine { +namespace Render +{ + class MouseLookEvent : public Mangle::Input::Event + { + Ogre::Camera* camera; + float sensX, sensY; // Mouse sensibility + bool flipProt; // Flip protection + + public: + MouseLookEvent(Ogre::Camera *cam=NULL, + float sX=0.2, float sY=0.2, + bool prot=true) + : camera(cam) + , sensX(sX) + , sensY(sY) + , flipProt(prot) + {} + + void setCamera(Ogre::Camera *cam) + { camera = cam; } + void setSens(float sX, float sY) + { sensX = sX; sensY = sY; } + void setProt(bool p) { flipProt = p; } + + void event(Type type, int index, const void *p); + }; + + typedef boost::shared_ptr MouseLookEventPtr; +}} +#endif diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp new file mode 100644 index 000000000..ecded08d8 --- /dev/null +++ b/libs/openengine/ogre/renderer.cpp @@ -0,0 +1,118 @@ +#include "renderer.hpp" +#include "fader.hpp" + +#include "OgreRoot.h" +#include "OgreRenderWindow.h" +#include "OgreLogManager.h" +#include "OgreLog.h" + +#include + +using namespace Ogre; +using namespace OEngine::Render; + +void OgreRenderer::cleanup() +{ + if (mFader) + delete mFader; + + if(mRoot) + delete mRoot; + mRoot = NULL; +} + +void OgreRenderer::start() +{ + mRoot->startRendering(); +} + +void OgreRenderer::update(float dt) +{ + mFader->update(dt); +} + +void OgreRenderer::screenshot(const std::string &file) +{ + mWindow->writeContentsToFile(file); +} + +float OgreRenderer::getFPS() +{ + return mWindow->getLastFPS(); +} + +bool OgreRenderer::configure(bool showConfig, + const std::string &cfgPath, + const std::string &logPath, + const std::string &pluginCfg, + bool _logging) +{ + // Set up logging first + new LogManager; + Log *log = LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); + logging = _logging; + + if(logging) + // Full log detail + log->setLogDetail(LL_BOREME); + else + // Disable logging + log->setDebugOutputEnabled(false); + + mRoot = new Root(pluginCfg, cfgPath, ""); + + // Show the configuration dialog and initialise the system, if the + // showConfig parameter is specified. The settings are stored in + // ogre.cfg. If showConfig is false, the settings are assumed to + // already exist in ogre.cfg. + int result; + if(showConfig) + result = mRoot->showConfigDialog(); + else + result = mRoot->restoreConfig(); + + return !result; +} + +bool OgreRenderer::configure(bool showConfig, + const std::string &cfgPath, + const std::string &pluginCfg, + bool _logging) +{ + return configure(showConfig, cfgPath, cfgPath, pluginCfg, _logging); +} + +bool OgreRenderer::configure(bool showConfig, + const std::string &pluginCfg, + bool _logging) +{ + return configure(showConfig, "", pluginCfg, _logging); +} + +void OgreRenderer::createWindow(const std::string &title) +{ + assert(mRoot); + // Initialize OGRE window + mWindow = mRoot->initialise(true, title, ""); +} + +void OgreRenderer::createScene(const std::string camName, float fov, float nearClip) +{ + assert(mRoot); + assert(mWindow); + // Get the SceneManager, in this case a generic one + mScene = mRoot->createSceneManager(ST_GENERIC); + + // Create the camera + mCamera = mScene->createCamera(camName); + mCamera->setNearClipDistance(nearClip); + mCamera->setFOVy(Degree(fov)); + + // Create one viewport, entire window + mView = mWindow->addViewport(mCamera); + + // Alter the camera aspect ratio to match the viewport + mCamera->setAspectRatio(Real(mView->getActualWidth()) / Real(mView->getActualHeight())); + + mFader = new Fader(); +} diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp new file mode 100644 index 000000000..f48383cbc --- /dev/null +++ b/libs/openengine/ogre/renderer.hpp @@ -0,0 +1,98 @@ +#ifndef OENGINE_OGRE_RENDERER_H +#define OENGINE_OGRE_RENDERER_H + +/* + Ogre renderer class + */ + +#include + +namespace Ogre +{ + class Root; + class RenderWindow; + class SceneManager; + class Camera; + class Viewport; +} + +namespace OEngine { +namespace Render +{ + class Fader; + class OgreRenderer + { + Ogre::Root *mRoot; + Ogre::RenderWindow *mWindow; + Ogre::SceneManager *mScene; + Ogre::Camera *mCamera; + Ogre::Viewport *mView; + Fader* mFader; + bool logging; + + public: + OgreRenderer() + : mRoot(NULL), mWindow(NULL), mScene(NULL), mFader(NULL) {} + ~OgreRenderer() { cleanup(); } + + /** Configure the renderer. This will load configuration files and + set up the Root and logging classes. */ + bool configure(bool showConfig, // Show config dialog box? + const std::string &cfgPath, // Path to directory where to store config files + const std::string &logPath, // Path to directory where to store log files + const std::string &pluginCfg, // plugin.cfg file + bool _logging); // Enable or disable logging + + bool configure(bool showConfig, // Show config dialog box? + const std::string &cfgPath, // Path to directory where to store config files + const std::string &pluginCfg, // plugin.cfg file + bool _logging); // Enable or disable logging + + /** Configure the renderer. This will load configuration files and + set up the Root and logging classes. */ + bool configure(bool showConfig, // Show config dialog box? + const std::string &pluginCfg, // plugin.cfg file + bool _logging); // Enable or disable logging + + /// Create a window with the given title + void createWindow(const std::string &title); + + /// Set up the scene manager, camera and viewport + void createScene(const std::string camName="Camera",// Camera name + float fov=55, // Field of view angle + float nearClip=5 // Near clip distance + ); + + /// Kill the renderer. + void cleanup(); + + /// Start the main rendering loop + void start(); + + void update(float dt); + + /// Write a screenshot to file + void screenshot(const std::string &file); + + float getFPS(); + + /// Get the Root + Ogre::Root *getRoot() { return mRoot; } + + /// Get the rendering window + Ogre::RenderWindow *getWindow() { return mWindow; } + + /// Get the scene manager + Ogre::SceneManager *getScene() { return mScene; } + + /// Get the screen colour fader + Fader *getFader() { return mFader; } + + /// Camera + Ogre::Camera *getCamera() { return mCamera; } + + /// Viewport + Ogre::Viewport *getViewport() { return mView; } + }; +}} +#endif diff --git a/libs/openengine/sound/sndmanager.cpp b/libs/openengine/sound/sndmanager.cpp new file mode 100644 index 000000000..02c6ba1e7 --- /dev/null +++ b/libs/openengine/sound/sndmanager.cpp @@ -0,0 +1,219 @@ +#include "sndmanager.hpp" + +#include "../misc/list.hpp" +#include + +using namespace OEngine::Sound; +using namespace Mangle::Sound; + +/** This is our own internal implementation of the + Mangle::Sound::Sound interface. This class links a SoundPtr to + itself and prevents itself from being deleted as long as the sound + is playing. + */ +struct OEngine::Sound::ManagedSound : SoundFilter +{ +private: + /** Who's your daddy? This is set if and only if we are listed + internally in the given SoundManager. + + It may be NULL if the manager has been deleted but the user + keeps their own SoundPtrs to the object. + */ + SoundManager *mgr; + + /** Keep a weak pointer to ourselves, which we convert into a + 'strong' pointer when we are playing. When 'self' is pointing to + ourselves, the object will never be deleted. + + This is used to make sure the sound is not deleted while + playing, unless it is explicitly ordered to do so by the + manager. + + TODO: This kind of construct is useful. If we need it elsewhere + later, template it. It would be generally useful in any system + where we poll to check if a resource is still needed, but where + manual references are allowed. + */ + WSoundPtr weak; + SoundPtr self; + + // Keep this object from being deleted + void lock() + { + self = SoundPtr(weak); + } + + // Release the lock. This may or may not delete the object. Never do + // anything after calling unlock()! + void unlock() + { + self.reset(); + } + +public: + // Used for putting ourselves in linked lists + ManagedSound *next, *prev; + + /** Detach this sound from its manager. This means that the manager + will no longer know we exist. Typically only called when either + the sound or the manager is about to get deleted. + + Since this means update() will no longer be called, we also have + to unlock the sound manually since it will no longer be able to + do that itself. This means that the sound may be deleted, even + if it is still playing, when the manager is deleted. + + However, you are still allowed to keep and manage your own + SoundPtr references, but the lock/unlock system is disabled + after the manager is gone. + */ + void detach() + { + if(mgr) + { + mgr->detach(this); + mgr = NULL; + } + + // Unlock must be last command. Object may get deleted at this + // point. + unlock(); + } + + ManagedSound(SoundPtr snd, SoundManager *mg) + : SoundFilter(snd), mgr(mg) + {} + ~ManagedSound() { detach(); } + + // Needed to set up the weak pointer + void setup(SoundPtr self) + { + weak = WSoundPtr(self); + } + + // Override play() to mark the object as locked + void play() + { + SoundFilter::play(); + + // Lock the object so that it is not deleted while playing. Only + // do this if we have a manager, otherwise the object will never + // get unlocked. + if(mgr) lock(); + } + + // Called regularly by the manager + void update() + { + // If we're no longer playing, don't force object retention. + if(!isPlaying()) + unlock(); + + // unlock() may delete the object, so don't do anything below this + // point. + } + + SoundPtr clone() + { + // Cloning only works when we have a manager. + assert(mgr); + return mgr->wrap(client->clone()); + } +}; + +struct SoundManager::SoundManagerList +{ +private: + // A linked list of ManagedSound objects. + typedef Misc::List SoundList; + SoundList list; + +public: + // Add a new sound to the list + void addNew(ManagedSound* snd) + { + list.insert(snd); + } + + // Remove a sound from the list + void remove(ManagedSound *snd) + { + list.remove(snd); + } + + // Number of sounds in the list + int numSounds() { return list.getNum(); } + + // Update all sounds + void updateAll() + { + ManagedSound *s = list.getHead(); + while(s) + { + ManagedSound *cur = s; + // Propagate first, since update() may delete object + s = s->next; + cur->update(); + } + } + + // Detach and unlock all sounds + void detachAll() + { + ManagedSound *s = list.getHead(); + while(s) + { + ManagedSound *cur = s; + s = s->next; + cur->detach(); + } + } +}; + +SoundManager::SoundManager(SoundFactoryPtr fact) + : FactoryFilter(fact) +{ + needsUpdate = true; + list = new SoundManagerList; +} + +SoundManager::~SoundManager() +{ + // Detach all sounds + list->detachAll(); +} + +SoundPtr SoundManager::wrap(SoundPtr client) +{ + // Create and set up the sound wrapper + ManagedSound *snd = new ManagedSound(client,this); + SoundPtr ptr(snd); + snd->setup(ptr); + + // Add ourselves to the list of all sounds + list->addNew(snd); + + return ptr; +} + +// Remove the sound from this manager. +void SoundManager::detach(ManagedSound *sound) +{ + list->remove(sound); +} + +int SoundManager::numSounds() +{ + return list->numSounds(); +} + +void SoundManager::update() +{ + // Update all the sounds we own + list->updateAll(); + + // Update the source if it needs it + if(client->needsUpdate) + client->update(); +} diff --git a/libs/openengine/sound/sndmanager.hpp b/libs/openengine/sound/sndmanager.hpp new file mode 100644 index 000000000..5ea0c4fc3 --- /dev/null +++ b/libs/openengine/sound/sndmanager.hpp @@ -0,0 +1,93 @@ +#ifndef OENGINE_SOUND_MANAGER_H +#define OENGINE_SOUND_MANAGER_H + +#include + +namespace OEngine +{ + namespace Sound + { + using namespace Mangle::Sound; + + class ManagedSound; + + /** A manager of Mangle::Sounds. + + The sound manager is a wrapper around the more low-level + SoundFactory - although it is also itself an implementation of + SoundFactory. It will: + - keep a list of all created sounds + - let you iterate the list + - keep references to playing sounds so you don't have to + - auto-release references to sounds that are finished playing + (ie. deleting them if you're not referencing them) + */ + class SoundManager : public FactoryFilter + { + // Shove the implementation details into the cpp file. + struct SoundManagerList; + SoundManagerList *list; + + // Create a new sound wrapper based on the given source sound. + SoundPtr wrap(SoundPtr snd); + + /** Internal function. Will completely disconnect the given + sound from this manager. Called from ManagedSound. + */ + friend class ManagedSound; + void detach(ManagedSound *sound); + public: + SoundManager(SoundFactoryPtr fact); + ~SoundManager(); + void update(); + + /// Get number of sounds currently managed by this manager. + int numSounds(); + + SoundPtr loadRaw(SampleSourcePtr input) + { return wrap(client->loadRaw(input)); } + + SoundPtr load(Mangle::Stream::StreamPtr input) + { return wrap(client->load(input)); } + + SoundPtr load(const std::string &file) + { return wrap(client->load(file)); } + + // Play a sound immediately, and release when done unless you + // keep the returned SoundPtr. + SoundPtr play(Mangle::Stream::StreamPtr sound) + { + SoundPtr snd = load(sound); + snd->play(); + return snd; + } + + SoundPtr play(const std::string &sound) + { + SoundPtr snd = load(sound); + snd->play(); + return snd; + } + + // Ditto for 3D sounds + SoundPtr play3D(Mangle::Stream::StreamPtr sound, float x, float y, float z) + { + SoundPtr snd = load(sound); + snd->setPos(x,y,z); + snd->play(); + return snd; + } + + SoundPtr play3D(const std::string &sound, float x, float y, float z) + { + SoundPtr snd = load(sound); + snd->setPos(x,y,z); + snd->play(); + return snd; + } + }; + + typedef boost::shared_ptr SoundManagerPtr; + } +} +#endif diff --git a/libs/openengine/sound/tests/Makefile b/libs/openengine/sound/tests/Makefile new file mode 100644 index 000000000..04952167f --- /dev/null +++ b/libs/openengine/sound/tests/Makefile @@ -0,0 +1,16 @@ +GCC=g++ -I../ + +all: sound_manager_test sound_3d_test + +L_FFMPEG=$(shell pkg-config --libs libavcodec libavformat) +L_OPENAL=$(shell pkg-config --libs openal) +L_AUDIERE=-laudiere + +sound_manager_test: sound_manager_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. + +sound_3d_test: sound_3d_test.cpp ../../mangle/sound/sources/audiere_source.cpp ../../mangle/sound/outputs/openal_out.cpp ../../mangle/stream/clients/audiere_file.cpp ../sndmanager.cpp + $(GCC) $^ -o $@ $(L_AUDIERE) $(L_OPENAL) -I../.. + +clean: + rm *_test diff --git a/libs/openengine/sound/tests/output/sound_3d_test.out b/libs/openengine/sound/tests/output/sound_3d_test.out new file mode 100644 index 000000000..a443c84f0 --- /dev/null +++ b/libs/openengine/sound/tests/output/sound_3d_test.out @@ -0,0 +1,3 @@ +Playing at 0,0,0 +Playing at 1,1,0 +Playing at -1,0,0 diff --git a/libs/openengine/sound/tests/output/sound_manager_test.out b/libs/openengine/sound/tests/output/sound_manager_test.out new file mode 100644 index 000000000..2b458493d --- /dev/null +++ b/libs/openengine/sound/tests/output/sound_manager_test.out @@ -0,0 +1,5 @@ +Playing ../../mangle/sound/tests/cow.wav +Replaying +pause +restart +Done playing. diff --git a/libs/openengine/sound/tests/sound_3d_test.cpp b/libs/openengine/sound/tests/sound_3d_test.cpp new file mode 100644 index 000000000..f5b197fd0 --- /dev/null +++ b/libs/openengine/sound/tests/sound_3d_test.cpp @@ -0,0 +1,46 @@ +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; +using namespace OEngine::Sound; + +const std::string sound = "../../mangle/sound/tests/cow.wav"; + +SoundManagerPtr m; + +// Play and wait for finish +void play(float x, float y, float z) +{ + cout << "Playing at " << x << "," << y << "," << z << endl; + + SoundPtr snd = m->play3D(sound,x,y,z); + + while(snd->isPlaying()) + { + usleep(10000); + m->update(); + } +} + +int main() +{ + SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); + SoundManagerPtr mg(new SoundManager(oaf)); + m = mg; + + mg->setListenerPos(0,0,0,0,1,0,0,0,1); + + play(0,0,0); + play(1,1,0); + play(-1,0,0); + + return 0; +} diff --git a/libs/openengine/sound/tests/sound_manager_test.cpp b/libs/openengine/sound/tests/sound_manager_test.cpp new file mode 100644 index 000000000..3794c4a3c --- /dev/null +++ b/libs/openengine/sound/tests/sound_manager_test.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace Mangle::Stream; +using namespace Mangle::Sound; +using namespace OEngine::Sound; + +const std::string sound = "../../mangle/sound/tests/cow.wav"; + +int main() +{ + SoundFactoryPtr oaf(new OpenAL_Audiere_Factory); + SoundManagerPtr mg(new SoundManager(oaf)); + + cout << "Playing " << sound << "\n"; + + assert(mg->numSounds() == 0); + + /** Start the sound playing, and then let the pointer go out of + scope. Lower-level players (like 'oaf' above) will immediately + delete the sound. SoundManager OTOH will keep it until it's + finished. + */ + mg->play(sound); + + assert(mg->numSounds() == 1); + + // Loop while there are still sounds to manage + while(mg->numSounds() != 0) + { + assert(mg->numSounds() == 1); + usleep(10000); + if(mg->needsUpdate) + mg->update(); + } + + SoundPtr snd = mg->play(sound); + cout << "Replaying\n"; + int i = 0; + while(mg->numSounds() != 0) + { + assert(mg->numSounds() == 1); + usleep(10000); + if(mg->needsUpdate) + mg->update(); + + if(i++ == 70) + { + cout << "pause\n"; + snd->pause(); + } + if(i == 130) + { + cout << "restart\n"; + snd->play(); + // Let the sound go out of scope + snd.reset(); + } + } + + cout << "Done playing.\n"; + + assert(mg->numSounds() == 0); + + return 0; +} diff --git a/libs/openengine/sound/tests/test.sh b/libs/openengine/sound/tests/test.sh new file mode 100755 index 000000000..2d07708ad --- /dev/null +++ b/libs/openengine/sound/tests/test.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +make || exit + +mkdir -p output + +PROGS=*_test + +for a in $PROGS; do + if [ -f "output/$a.out" ]; then + echo "Running $a:" + ./$a | diff output/$a.out - + else + echo "Creating $a.out" + ./$a > "output/$a.out" + git add "output/$a.out" + fi +done diff --git a/libs/openengine/testall.sh b/libs/openengine/testall.sh new file mode 100755 index 000000000..097fdabd5 --- /dev/null +++ b/libs/openengine/testall.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +function run() +{ + echo "TESTING $1" + cd "$1/tests/" + ./test.sh + cd ../../ +} + +run input