From 65ce3c6ba5512885d1e0b252f539a4e66d084d82 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Aug 2013 02:51:19 -0700 Subject: [PATCH] Use a better method to do actor physics traces --- apps/openmw/mwworld/physicssystem.cpp | 35 ++++++------ libs/openengine/bullet/physic.hpp | 48 +++++++++------- libs/openengine/bullet/trace.cpp | 82 +++++++++++++++++++++++++-- libs/openengine/bullet/trace.h | 4 ++ 4 files changed, 124 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index e8450e1d2..ea013bc7a 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -41,24 +41,21 @@ namespace MWWorld return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees(); } - static bool stepMove(Ogre::Vector3& position, const Ogre::Quaternion& orient, + static bool stepMove(btCollisionObject *colobj, Ogre::Vector3 &position, const Ogre::Vector3 &velocity, float &remainingTime, - const Ogre::Vector3 &halfExtents, bool isInterior, OEngine::Physic::PhysicEngine *engine) { traceResults trace; - newtrace(&trace, orient, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), - halfExtents, isInterior, engine); + actortrace(&trace, colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), engine); if(trace.fraction == 0.0f) return false; - newtrace(&trace, orient, trace.endpos, trace.endpos + velocity*remainingTime, - halfExtents, isInterior, engine); + actortrace(&trace, colobj, trace.endpos, trace.endpos + velocity*remainingTime, engine); if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope)) return false; float movefrac = trace.fraction; - newtrace(&trace, orient, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine); + actortrace(&trace, colobj, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), engine); if(getSlope(trace.planenormal) <= sMaxSlope) { // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. @@ -100,7 +97,7 @@ namespace MWWorld bool wasCollisionMode = physicActor->getCollisionMode(); physicActor->enableCollisions(false); - Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); + Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); halfExtents.z = 1; // we trace the feet only, so we use a very thin box Ogre::Vector3 newPosition = position; @@ -133,15 +130,15 @@ namespace MWWorld if(!physicActor || !physicActor->getCollisionMode()) { // FIXME: This works, but it's inconcsistent with how the rotations are applied elsewhere. Why? - return position + (Ogre::Quaternion(Ogre::Radian( -refpos.rot[2]), Ogre::Vector3::UNIT_Z)* - Ogre::Quaternion(Ogre::Radian( -refpos.rot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * + return position + (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)* + Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)* + Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * movement; } - bool isInterior = !ptr.getCell()->isExterior(); - Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); - physicActor->enableCollisions(false); + btCollisionObject *colobj = physicActor->getCollisionBody(); + Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); + position.z += halfExtents.z; traceResults trace; bool onground = false; @@ -158,7 +155,7 @@ namespace MWWorld { if(!(movement.z > 0.0f)) { - newtrace(&trace, orient, position, position-Ogre::Vector3(0,0,4), halfExtents, isInterior, engine); + actortrace(&trace, colobj, position, position-Ogre::Vector3(0,0,4), engine); if(trace.fraction < 1.0f && getSlope(trace.planenormal) <= sMaxSlope) onground = true; } @@ -178,7 +175,7 @@ namespace MWWorld for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations) { // trace to where character would go if there were no obstructions - newtrace(&trace, orient, newPosition, newPosition+velocity*remainingTime, halfExtents, isInterior, engine); + actortrace(&trace, colobj, newPosition, newPosition+velocity*remainingTime, engine); // check for obstructions if(trace.fraction >= 1.0f) @@ -190,7 +187,7 @@ namespace MWWorld //std::cout<<"angle: "<setOnGround(onground); physicActor->setVerticalForce(!onground ? velocity.z - time*627.2f : 0.0f); - physicActor->enableCollisions(true); + newPosition.z -= halfExtents.z; return newPosition; } }; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 32af0da4e..9b1541ea2 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -66,6 +66,20 @@ namespace Physic std::string mName; }; + /** + *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); + virtual ~RigidBody(); + std::string mName; + bool mPlaceable; + }; + /** * A physic actor uses a rigid body based on box shapes. * Pmove is used to move the physic actor around the dynamic world. @@ -129,47 +143,41 @@ namespace Physic bool getOnGround() const; + btCollisionObject *getCollisionBody() const + { + return mBody; + } + private: void disableCollisionBody(); void enableCollisionBody(); public: //HACK: in Visual Studio 2010 and presumably above, this structures alignment -// must be 16, but the built in operator new & delete don't properly -// perform this alignment. +// must be 16, but the built in operator new & delete don't properly +// perform this alignment. #if _MSC_VER >= 1600 - void * operator new (size_t Size) { return _aligned_malloc (Size, 16); } - void operator delete (void * Data) { _aligned_free (Data); } + void * operator new (size_t Size) { return _aligned_malloc (Size, 16); } + void operator delete (void * Data) { _aligned_free (Data); } #endif private: - OEngine::Physic::RigidBody* mBody; OEngine::Physic::RigidBody* mRaycastingBody; + Ogre::Vector3 mBoxScaledTranslation; - btQuaternion mBoxRotationInverse; Ogre::Quaternion mBoxRotation; + btQuaternion mBoxRotationInverse; + float verticalForce; bool onGround; bool collisionMode; + std::string mMesh; - PhysicEngine* mEngine; std::string mName; + PhysicEngine *mEngine; }; - /** - *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); - virtual ~RigidBody(); - std::string mName; - bool mPlaceable; - }; struct HeightField { diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index d246417c7..08c42d9d0 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -8,12 +8,6 @@ #include "physic.hpp" -enum traceWorldType -{ - collisionWorldTrace = 1, - pickWorldTrace = 2, - bothWorldTrace = collisionWorldTrace | pickWorldTrace -}; void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine *enginePass) //Traceobj was a Aedra Object { @@ -46,3 +40,79 @@ void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre: results->fraction = 1.0f; } } + + +class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback +{ +public: + ClosestNotMeConvexResultCallback(btCollisionObject *me, const btVector3 &up, btScalar minSlopeDot) + : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), + mMe(me), mUp(up), mMinSlopeDot(minSlopeDot) + { + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) + { + if(convexResult.m_hitCollisionObject == mMe) + 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 = mUp.dot(hitNormalWorld); + if(dotUp < mMinSlopeDot) + return btScalar(1); + + return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); + } + +protected: + btCollisionObject *mMe; + const btVector3 mUp; + const btScalar mMinSlopeDot; +}; + +void actortrace(traceResults *results, btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, OEngine::Physic::PhysicEngine *enginePass) +{ + const btVector3 btstart(start.x, start.y, start.z); + const btVector3 btend(end.x, end.y, end.z); + + const btTransform &trans = actor->getWorldTransform(); + btTransform from(trans); + btTransform to(trans); + from.setOrigin(btstart); + to.setOrigin(btend); + + ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0)); + newTraceCallback.m_collisionFilterMask = OEngine::Physic::CollisionType_World | + OEngine::Physic::CollisionType_HeightMap | + OEngine::Physic::CollisionType_Actor; + + btCollisionShape *shape = actor->getCollisionShape(); + assert(shape->isConvex()); + enginePass->dynamicsWorld->convexSweepTest(static_cast(shape), + from, to, newTraceCallback); + + // Copy the hit data over to our trace results struct: + if(newTraceCallback.hasHit()) + { + const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; + results->fraction = newTraceCallback.m_closestHitFraction; + results->planenormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); + results->endpos = (end-start)*results->fraction + start; + } + else + { + results->endpos = end; + results->planenormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); + results->fraction = 1.0f; + } +} diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index cd2547f8c..79c6e72ef 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -13,6 +13,9 @@ namespace OEngine } +class btCollisionObject; + + struct traceResults { Ogre::Vector3 endpos; @@ -22,5 +25,6 @@ struct traceResults }; void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); +void actortrace(traceResults *results, btCollisionObject *actor, const Ogre::Vector3& start, const Ogre::Vector3& end, OEngine::Physic::PhysicEngine *enginePass); #endif