forked from teamnwah/openmw-tes3coop
Merged openengine into openmw
commit
b4343431c6
@ -1,3 +0,0 @@
|
||||
[submodule "libs/openengine"]
|
||||
path = libs/openengine
|
||||
url = git://github.com/zinnschlag/OpenEngine
|
@ -1 +0,0 @@
|
||||
Subproject commit 8f98718315fe11af359740c4a025fd1ca52a9157
|
@ -0,0 +1,3 @@
|
||||
*~
|
||||
*.o
|
||||
*_test
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -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<Ogre::Vector3> 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<Vector3> 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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<unsigned char, Vector3Array*> BoneIndex;
|
||||
typedef std::pair<unsigned short, Vector3Array*> 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
|
@ -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
|
@ -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<btCompoundShape*>(Shape);
|
||||
int a = ms->getNumChildShapes();
|
||||
for(int i=0; i <a;i++)
|
||||
{
|
||||
deleteShape(ms->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<BulletShapeManager>::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)
|
||||
{}
|
@ -0,0 +1,138 @@
|
||||
#ifndef _BULLET_SHAPE_LOADER_H_
|
||||
#define _BULLET_SHAPE_LOADER_H_
|
||||
|
||||
#include <OgreResource.h>
|
||||
#include <OgreResourceManager.h>
|
||||
#include <btBulletCollisionCommon.h>
|
||||
|
||||
//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<BulletShape>
|
||||
{
|
||||
public:
|
||||
BulletShapePtr() : Ogre::SharedPtr<BulletShape>() {}
|
||||
explicit BulletShapePtr(BulletShape *rep) : Ogre::SharedPtr<BulletShape>(rep) {}
|
||||
BulletShapePtr(const BulletShapePtr &r) : Ogre::SharedPtr<BulletShape>(r) {}
|
||||
BulletShapePtr(const Ogre::ResourcePtr &r) : Ogre::SharedPtr<BulletShape>()
|
||||
{
|
||||
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<BulletShape*>(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<BulletShape*>(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<BulletShape*>(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<BulletShapeManager>
|
||||
{
|
||||
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
|
@ -0,0 +1,45 @@
|
||||
#include "CMotionState.h"
|
||||
#include "physic.hpp"
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <btBulletCollisionCommon.h>
|
||||
#include <components/nifbullet/bullet_nif_loader.hpp>
|
||||
//#include <apps\openmw\mwworld\world.hpp>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
@ -0,0 +1,52 @@
|
||||
#ifndef OENGINE_CMOTIONSTATE_H
|
||||
#define OENGINE_CMOTIONSTATE_H
|
||||
|
||||
#include <BulletDynamics/Dynamics/btRigidBody.h>
|
||||
#include <string>
|
||||
|
||||
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
|
@ -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 )
|
||||
{
|
||||
}
|
@ -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
|
@ -0,0 +1,375 @@
|
||||
#include "physic.hpp"
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <btBulletCollisionCommon.h>
|
||||
#include <components/nifbullet/bullet_nif_loader.hpp>
|
||||
//#include <apps\openmw\mwworld\world.hpp>
|
||||
#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, //<Collide with nothing
|
||||
COL_WORLD = BIT(0), //<Collide with world objects
|
||||
COL_ACTOR_INTERNAL = BIT(1), //<Collide internal capsule
|
||||
COL_ACTOR_EXTERNAL = BIT(2) //<collide with external capsule
|
||||
};
|
||||
|
||||
PhysicActor::PhysicActor(std::string name)
|
||||
{
|
||||
mName = name;
|
||||
|
||||
// The capsule is at the origin
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
|
||||
// External capsule
|
||||
externalGhostObject = new PairCachingGhostObject(name);
|
||||
externalGhostObject->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<std::string,RigidBody*>::iterator it = RigidBodyMap.find(name);
|
||||
if (it != RigidBodyMap.end() )
|
||||
{
|
||||
RigidBody* body = it->second;
|
||||
if(body != NULL)
|
||||
{
|
||||
// broadphase->getOverlappingPairCache()->removeOverlappingPairsContainingProxy(body->getBroadphaseProxy(),dispatcher);
|
||||
/*std::map<std::string,PhysicActor*>::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<std::string,RigidBody*>::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<std::string,PhysicActor*>::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<std::string,PhysicActor*>::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<std::string,float> 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<RigidBody&>(*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<PairCachingGhostObject&>(*resultCallback2.m_collisionObject).mName;
|
||||
d = d2;
|
||||
}
|
||||
}
|
||||
|
||||
return std::pair<std::string,float>(name,d);
|
||||
}
|
||||
}};
|
@ -0,0 +1,230 @@
|
||||
#ifndef OENGINE_BULLET_PHYSIC_H
|
||||
#define OENGINE_BULLET_PHYSIC_H
|
||||
|
||||
#include <BulletDynamics/Dynamics/btRigidBody.h>
|
||||
#include "BulletCollision/CollisionDispatch/btGhostObject.h"
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#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<std::string,float> rayTest(btVector3& from,btVector3& to);
|
||||
|
||||
//event list of non player object
|
||||
std::list<PhysicEvent> NPEventList;
|
||||
|
||||
//event list affecting the player
|
||||
std::list<PhysicEvent> PEventList;
|
||||
|
||||
//Bullet Stuff
|
||||
btBroadphaseInterface* broadphase;
|
||||
btDefaultCollisionConfiguration* collisionConfiguration;
|
||||
btSequentialImpulseConstraintSolver* solver;
|
||||
btCollisionDispatcher* dispatcher;
|
||||
btDiscreteDynamicsWorld* dynamicsWorld;
|
||||
|
||||
//the NIF file loader.
|
||||
BulletShapeLoader* mShapeLoader;
|
||||
|
||||
std::map<std::string,RigidBody*> RigidBodyMap;
|
||||
std::map<std::string,PhysicActor*> PhysicActorMap;
|
||||
|
||||
//debug rendering
|
||||
BtOgre::DebugDrawer* mDebugDrawer;
|
||||
bool isDebugCreated;
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
@ -0,0 +1,80 @@
|
||||
#include <MyGUI.h>
|
||||
#include <OIS/OIS.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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 <typename X>
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
#ifndef OENGINE_MYGUI_EVENTS_H
|
||||
#define OENGINE_MYGUI_EVENTS_H
|
||||
|
||||
#include <mangle/input/event.hpp>
|
||||
|
||||
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<EventInjector> EventInjectorPtr;
|
||||
}}
|
||||
#endif
|
@ -0,0 +1,119 @@
|
||||
#ifndef OENGINE_MYGUI_LAYOUT_H
|
||||
#define OENGINE_MYGUI_LAYOUT_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <MyGUI.h>
|
||||
|
||||
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 <typename T>
|
||||
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<T>(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<MyGUI::StaticText*>(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
|
@ -0,0 +1,47 @@
|
||||
#include <MyGUI.h>
|
||||
#include <MyGUI_OgrePlatform.h>
|
||||
#include <assert.h>
|
||||
|
||||
#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;
|
||||
}
|
@ -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
|
@ -0,0 +1,75 @@
|
||||
#ifndef OENGINE_INPUT_DISPATCHMAP_H
|
||||
#define OENGINE_INPUT_DISPATCHMAP_H
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <assert.h>
|
||||
|
||||
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<int> OutList;
|
||||
typedef std::map<int, OutList> 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
|
@ -0,0 +1,78 @@
|
||||
#ifndef OENGINE_INPUT_DISPATCHER_H
|
||||
#define OENGINE_INPUT_DISPATCHER_H
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
|
||||
#include <mangle/input/event.hpp>
|
||||
|
||||
#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<Dispatcher> DispatcherPtr;
|
||||
|
||||
}}
|
||||
#endif
|
@ -0,0 +1,106 @@
|
||||
#ifndef OENGINE_INPUT_FUNCBINDER_H
|
||||
#define OENGINE_INPUT_FUNCBINDER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/function.hpp>
|
||||
#include <assert.h>
|
||||
|
||||
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<void(int,const void*)> 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<FuncBinding> 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
|
@ -0,0 +1,46 @@
|
||||
#ifndef OENGINE_INPUT_POLLER_H
|
||||
#define OENGINE_INPUT_POLLER_H
|
||||
|
||||
#include "dispatch_map.hpp"
|
||||
#include <mangle/input/driver.hpp>
|
||||
|
||||
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
|
@ -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
|
@ -0,0 +1,54 @@
|
||||
#include <iostream>
|
||||
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;
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
#include <iostream>
|
||||
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;
|
||||
}
|
@ -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
|
@ -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
|
@ -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!
|
@ -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!
|
@ -0,0 +1,71 @@
|
||||
#include <iostream>
|
||||
#include <mangle/input/servers/sdl_driver.hpp>
|
||||
#include <SDL.h>
|
||||
#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;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
#include <iostream>
|
||||
#include <mangle/input/servers/sdl_driver.hpp>
|
||||
#include <SDL.h>
|
||||
|
||||
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;
|
||||
}
|
@ -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
|
@ -0,0 +1,178 @@
|
||||
#ifndef MISC_LIST_H
|
||||
#define MISC_LIST_H
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
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 <typename Elem>
|
||||
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
|
@ -0,0 +1 @@
|
||||
old
|
@ -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 <OgreFrameListener.h>
|
||||
#include <OgreRenderWindow.h>
|
||||
|
||||
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
|
@ -0,0 +1,136 @@
|
||||
#include "fader.hpp"
|
||||
|
||||
#include <OgreOverlayManager.h>
|
||||
#include <OgreOverlayContainer.h>
|
||||
#include <OgreOverlay.h>
|
||||
#include <OgreMaterial.h>
|
||||
#include <OgreTechnique.h>
|
||||
#include <OgreMaterialManager.h>
|
||||
#include <OgreResourceGroupManager.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#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;
|
||||
}
|
@ -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 <OgreFrameListener.h>
|
||||
|
||||
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
|
@ -0,0 +1,57 @@
|
||||
#include "mouselook.hpp"
|
||||
|
||||
#include <OIS/OIS.h>
|
||||
#include <OgreCamera.h>
|
||||
#include <OgreSceneNode.h>
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <mangle/input/event.hpp>
|
||||
|
||||
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<MouseLookEvent> MouseLookEventPtr;
|
||||
}}
|
||||
#endif
|
@ -0,0 +1,118 @@
|
||||
#include "renderer.hpp"
|
||||
#include "fader.hpp"
|
||||
|
||||
#include "OgreRoot.h"
|
||||
#include "OgreRenderWindow.h"
|
||||
#include "OgreLogManager.h"
|
||||
#include "OgreLog.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
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();
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
#ifndef OENGINE_OGRE_RENDERER_H
|
||||
#define OENGINE_OGRE_RENDERER_H
|
||||
|
||||
/*
|
||||
Ogre renderer class
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
@ -0,0 +1,219 @@
|
||||
#include "sndmanager.hpp"
|
||||
|
||||
#include "../misc/list.hpp"
|
||||
#include <boost/weak_ptr.hpp>
|
||||
|
||||
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<ManagedSound> 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();
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
#ifndef OENGINE_SOUND_MANAGER_H
|
||||
#define OENGINE_SOUND_MANAGER_H
|
||||
|
||||
#include <mangle/sound/filters/pure_filter.hpp>
|
||||
|
||||
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<SoundManager> SoundManagerPtr;
|
||||
}
|
||||
}
|
||||
#endif
|
@ -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
|
@ -0,0 +1,3 @@
|
||||
Playing at 0,0,0
|
||||
Playing at 1,1,0
|
||||
Playing at -1,0,0
|
@ -0,0 +1,5 @@
|
||||
Playing ../../mangle/sound/tests/cow.wav
|
||||
Replaying
|
||||
pause
|
||||
restart
|
||||
Done playing.
|
@ -0,0 +1,46 @@
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <assert.h>
|
||||
|
||||
#include <mangle/stream/servers/file_stream.hpp>
|
||||
#include <mangle/sound/filters/openal_audiere.hpp>
|
||||
|
||||
#include <sound/sndmanager.hpp>
|
||||
|
||||
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;
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <assert.h>
|
||||
|
||||
#include <mangle/stream/servers/file_stream.hpp>
|
||||
#include <mangle/sound/filters/openal_audiere.hpp>
|
||||
|
||||
#include <sound/sndmanager.hpp>
|
||||
|
||||
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;
|
||||
}
|
@ -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
|
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
function run()
|
||||
{
|
||||
echo "TESTING $1"
|
||||
cd "$1/tests/"
|
||||
./test.sh
|
||||
cd ../../
|
||||
}
|
||||
|
||||
run input
|
Loading…
Reference in New Issue